狂补dp Atcoder Educational DP Contest(全)
狂补dp Atcoder Educational DP Contest(全)
注意
- 为了减少篇幅,本博客代码部分只放主代码部分,其余省略快读、快输、头文件等代码,即把代码看做伪代码更佳
A - Frog 1
- 幼儿级别的dp,直接dp便可
void problem() {int n = IO();for (int i = 0; i < n; ++i) num[i] = IO();for (int i = 1; i < n; ++i) {dp[i] = dp[i - 1] + abs(num[i] - num[i - 1]);if (i >= 2) dp[i] = min(dp[i], dp[i - 2] + abs(num[i] - num[i - 2]));}print(dp[n - 1]);
}
B - Frog 2
- 还是幼儿级别,比A题多了一重循环
void problem() {int n = IO(), k = IO();for (int i = 0; i < n; ++i) num[i] = IO();for (int i = 1; i < n; ++i) {dp[i] = dp[i - 1] + abs(num[i] - num[i - 1]);for (int j = 2; j <= min(k, i); ++j) {if (i >= j) dp[i] = min(dp[i], dp[i - j] + abs(num[i] - num[i - j]));}}print(dp[n - 1]);
}
C - Vacation
- 根据题意定义一个二维dp,第二维表示当前选的是哪个,然后转移就很简单了
void problem() {int n = IO();for (int i = 1; i <= n; ++i) a[i] = IO(), b[i] = IO(), c[i] = IO();for (int i = 1; i <= n; ++i) {dp[i][0] = max(dp[i - 1][1], dp[i - 1][2]) + a[i];dp[i][1] = max(dp[i - 1][0], dp[i - 1][2]) + b[i];dp[i][2] = max(dp[i - 1][1], dp[i - 1][0]) + c[i];}print(max({dp[n][0], dp[n][1], dp[n][2]}));
}
D - Knapsack 1
- 01背包问题
void problem() {int n = IO(), m = IO();for (int i = 1; i <= n; ++i) w[i] = IO(), v[i] = IO();for (int i = 1; i <= n; ++i) {for (int j = m; j >= w[i]; --j) dp[j] = max(dp[j], dp[j - w[i]] + v[i]);}print(dp[m]);
}
E - Knapsack 2
- 01背包+换意dp
- 原本定义
dp[i]
表示容量不超过i
能装的最大价值 - 由于背包容量过大,但我们不难发现物品的价值不大,可以对状态定义进行换意
- 即定义
dp[i]
表示装价值为i
物品所花费的最小容量
void problem() {int n = IO(), m = IO();for (int i = 1; i <= n; ++i) w[i] = IO(), v[i] = IO();fill_n(dp, N, INF);dp[0] = 0;for (int i = 1; i <= n; ++i) {for (int j = 100000; j >= v[i]; --j) {dp[j] = min(dp[j], dp[j - v[i]] + w[i]);}}for (int i = 100000; i >= 0; --i) {if (dp[i] <= m) {print(i);break;}}
}
F - LCS
- LCS+dp求具体方案
- dp求具体方案我还是喜欢递归找,复杂度O(nnn)
void solve(int n, int m) {if (n == 0 || m == 0) {reverse(all(ans));for (char& c : ans) putchar(c);exit(0);return;}if (dp[n][m] == dp[n - 1][m]) solve(n - 1, m);else if (dp[n][m] == dp[n][m - 1]) solve(n, m - 1);else {ans.push_back(a[n]);solve(n - 1, m - 1);}
}void problem() {scanf("%s\n%s", a + 1, b + 1);int n = strlen(a + 1), m = strlen(b + 1);for (int i = 1; i <= n; ++i) {for (int j = 1; j <= m; ++j) {if (a[i] == b[j]) dp[i][j] = dp[i - 1][j - 1] + 1;else dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]);}}solve(n, m);
}
G - Longest Path
- 拓扑dp
- 可以bfs转移,但我是dfs转移的
- 值得注意的是,状态定义为
dp[i]
表示从i
点出发能走的最远距离好转移一些
int dfs(int u) {if (dp[u] != -1) return dp[u];int& res = dp[u] = 0;for (int& v : mp[u]) {res = max(res, dfs(v) + 1);}return res;
}
void problem() {int n = IO(), m = IO();for (int i = 0; i < m; ++i) {int u = IO(), v = IO();mp[u].pb(v);++du[v];}fill_n(dp + 1, n, -1);for (int i = 1; i <= n; ++i) if (!du[i]) ans = max(dfs(i), ans);print(ans, '\n');
}
H - Grid 1
- 走格子,老dp模型了
void problem() {int n = IO(), m = IO();for (int i = 1; i <= n; ++i) scanf("%s", g[i] + 1);dp[0][1] = 1;for (int i = 1; i <= n; ++i) {for (int j = 1; j <= m; ++j) {if (g[i][j] != '.') continue;dp[i][j] = (dp[i][j - 1] + dp[i - 1][j]) % mod;}}print(dp[n][m]);
}
I - Coins
- 简单的一个概率dp
- 定义
dp[i][j]
表示前i
个硬币中人头朝上的个数为j
的概率
void problem() {int n = IO();for (int i = 1; i <= n; ++i) scanf("%lf", p + i);dp[0][0] = 1;for (int i = 1; i <= n; ++i) {dp[i][0] = dp[i - 1][0] * (1.0 - p[i]);for (int j = 1; j <= i; ++j) {dp[i][j] += dp[i - 1][j - 1] * p[i] + dp[i - 1][j] * (1.0 - p[i]);}}double ans;for (int i = 1; i <= n; ++i) {if (i > n - i) ans += dp[n][i];}printf("%.9f\n", ans);
}
J - Sushi
- 期望dp
- 突破口就是每个盘子上的寿司不超过3个,计数寿司个数为
i
的盘子有几个 - 定义
dp[i][j][k]
表示剩下i
个盘子上面只有1个寿司,j
个盘子上面只有2个寿司,k
个盘子只有3个寿司的期望值 - 转移的话,递归记忆化转移比较方便
double dfs(int a, int b, int c) {if (a + b + c == 0) return 0;if (vis[a][b][c]) return dp[a][b][c];vis[a][b][c] = 1;double tot = a + b + c;double &res = dp[a][b][c] = n * 1.0 / tot;if (a) res += dfs(a - 1, b, c) * a / tot;if (b) res += dfs(a + 1, b - 1, c) * b / tot;if (c) res += dfs(a, b + 1, c - 1) * c / tot;return res;
}void problem() {n = IO();int num[4] = {0};for (int i = 0; i < n; ++i) {int x = IO();++num[x];}printf("%.10f", dfs(num[1], num[2], num[3]));
}
K - Stones
- 基础博弈dp
void problem() {int n = IO(), k = IO();for (int i = 0; i < n; ++i) a[i] = IO();dp[0] = 0;for (int i = 1; i <= k; ++i) {int ok = 0;for (int j = 0; j < n; ++j) {if (i < a[j]) continue;if (dp[i - a[j]] == 0) {ok = 1; // 如果有一个必输点,则他必赢break;}}dp[i] = ok;}puts(dp[k] ? "First" : "Second");
}
L - Deque
- 区间dp+思维
- 转变一下思路,先手希望
X-Y
最大,后手希望X-Y
最小,即后手希望Y-X
最大,即都希望自己的分数减去对手的分数达到最大 - 设
dp[l][r]
表示[l, r]
区间中先手分数减去后手分数的最大值,转移的话,便是用决策 加上 负数的dp值 - 即
dp[l][r] = max(a[r] + (-dp[l][r - 1]), a[l] + (-dp[l + 1][r]))
void problem() {int n = IO();for (int i = 1; i <= n; ++i) a[i] = IO(), dp[i][i] = a[i];for (int i = n; i; --i) {for (int j = i; j <= n; ++j) {dp[i][j] = max(a[j] - dp[i][j - 1], a[i] - dp[i + 1][j]);}}print(dp[1][n]);
}
M - Candies
- 多重背包问题
- 直接三重循环的话会超时,像这样的代码
dp[0][0] = 1;
for (int i = 1; i <= n; ++i) {for (int j = 0; j <= m; ++j) {for (int k = 0; k <= min(a[i], j); ++k) {dp[i][j] += dp[i - 1][j - k];}}
}
- 但我们不难发现第三重循环是可以优化掉的
- 分析
dp[i][j]
他的转移只会从dp[i - 1][x]
(其中j - a[i] <= x <= j
)转移过来,固我们可以用前缀和与滑动窗口的思想来进行优化掉第三重循环
void problem() {int n = IO(), m = IO();for (int i = 1; i <= n; ++i) a[i] = IO();dp[0][0] = 1;for (int i = 1; i <= n; ++i) {ll sum = 0;for (int j = 0; j <= m; ++j) {sum += dp[i - 1][j];dp[i][j] = (sum + mod) % mod;if (j >= a[i]) sum -= dp[i - 1][j - a[i]];}}print(dp[n][m]);
}
N - Slimes
- 区间dp入门题
void problem() {int n = IO();memset(dp, 0x3f, sizeof dp);for (int i = 1; i <= n; ++i) a[i] = IO(), sum[i] = sum[i - 1] + a[i], dp[i][i] = 0;for (int i = n; i >= 1; --i) {for (int j = i; j <= n; ++j) { // [i, j] 区间for (int k = i; k < j; ++k) {dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1]);}}}print(dp[1][n]);
}
O - Matching
- 状压dp
- 本质就是二分图完美匹配的方案数
- 设
dp[i][j]
表示正在决策第i
(下标从0开始)个男生,且前i
个男生匹配好的女生为j
(二进制) - 转移用刷表法转移比较好
ll dp[22][(1 << 21) + 10];
vector<int> vt[22];int count(int x) {int res = 0;while (x) {x &= x - 1;res++;}return res;
}int m[25][25];
void problem() {int n = IO();for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {m[i][j] = IO();}}dp[0][0] = 1;int len = 1 << n;for (int i = 0; i < len; ++i) vt[count(i)].push_back(i);for (int i = 0; i < n; ++i) {for (int j : vt[i]) {for (int k = 0; k < n; ++k) {if (j & (1 << k)) continue;if (!m[i][k]) continue;dp[i + 1][j ^ (1 << k)] += dp[i][j];dp[i + 1][j ^ (1 << k)] %= mod;}}}print(dp[n][len - 1]);
}
P - Independent Set
- 树上dp
- 有点类似树上独立集了
vector<int> mp[N];
ll dp[N][2]; // i号结点为0/1色的方案数void dfs(int u, int fa) {dp[u][0] = dp[u][1] = 1;for (int& v : mp[u]) {if (v == fa) continue;dfs(v, u);dp[u][1] = dp[v][0] * dp[u][1] % mod;dp[u][0] = dp[u][0] * (dp[v][0] + dp[v][1]) % mod;}
}void problem() {int n = IO();for (int i = 1; i < n; ++i) {int u = IO(), v = IO();mp[u].pb(v), mp[v].pb(u);}dfs(1, 0);printf((dp[1][0] + dp[1][1]) % mod);
}
Q - Flowers
- LIS的变种,需要用线段树优化
const int N = 2e5 + 5;
struct pairs {int indx, h;ll a;
}p[N];ll seg[N << 2];void update(int indx, ll v, int l, int r, int node = 1) {if (l == r) {seg[node] = v;return;}int mid = (l + r) >> 1;if (indx <= mid) update(indx, v, l, mid, node << 1);else update(indx, v, mid + 1, r, node << 1 | 1);seg[node] = max(seg[node << 1], seg[node << 1 | 1]);
}ll get(int ql, int qr, int l, int r, int node = 1) {if (ql <= l && r <= qr) return seg[node];int mid = (l + r) >> 1;ll ret = 0;if (ql <= mid) ret = get(ql, qr, l, mid, node << 1);if (qr > mid) ret = max(ret, get(ql, qr, mid + 1, r, node << 1 | 1));return ret;
}void problem() {int n = IO();for (int i = 1; i <= n; ++i) {p[i].indx = i;p[i].h = IO();}for (int i = 1; i <= n; ++i) p[i].a = IO();sort(p + 1, p + 1 + n, [](pairs& i, pairs& j) {return i.h < j.h;});ll ans = 0;for (int i = 1; i <= n; ++i) {ll maxv = get(1, p[i].indx, 1, n) + p[i].a;update(p[i].indx, maxv, 1, n);ans = max(maxv, ans);}printf(ans);
}
R - Walk
- 不多说,考的就是矩阵乘法在图中的意义(结论题)
- 矩阵A的n次幂表示走n次x到y的方案数,对于本题就是求矩阵的k次幂,再求结果矩阵上各个点上的和即可
#define vi vector<ll>
#define Mat vector<vi>Mat operator* (Mat a, Mat b) {Mat res(N, vi(N, 0));for (int i = 0; i < N; ++i) for (int j = 0; j < N; ++j)for (int k = 0; k < N; ++k)res[i][j] = (res[i][j] + a[i][k] * b[k][j] % mod) % mod;return res;
}Mat operator^ (Mat a, long long b) {Mat res(N, vi(N, 0));for (int i = 0; i < N; ++i) res[i][i] = 1;while (b) {if (b & 1) res = res * a;a = a * a, b >>= 1;}return res;
}void problem() {ll n = IO(), k = IO();Mat mat(N, vi(N, 0));for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {mat[i][j] = IO();}}Mat res = mat ^ k;ll ans = 0;for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {ans = (res[i][j] + ans) % mod;}}printf(ans);
}
S - Digit Sum
- 数位dp入门题
- 对于数位dp,记忆化搜比较好理解
int num[N], len, n;ll dp[N][2][101];
ll dfs(int x, int limit, int m) {ll &res = dp[x][limit][m];if (~res) return res;if (x == 0) return res = (m == 0);res = 0;int up = (limit ? num[x] : 9);for (int i = 0; i <= up; ++i) {res += dfs(x - 1, limit && i == up, (m + i) % n);res %= mod;}return res;
}void problem() {string s;cin >> s >> n;len = s.size();for (int i = 0, j = len; i < len; ++i, --j) num[j] = s[i] - '0';memset(dp, -1, sizeof dp);print(((dfs(len, 1, 0) - 1) % mod + mod) % mod);
}
T - Permutation
- 比较思维的一道dp题
- 设
dp[i][j]
表示前i
个排列中(包括i
)最后一位放的是j
的方案数 - 转移的话要注意,当前第
i
个放的j
,转移利用到的第i - 1
的排列中将小于j
的视为小于j
,大于等于j
的视为严格大于j
- 由于转移的时候是要三重循环,但草稿可以发现第三种循环可以优化掉的,看代码就懂了
int n;
string s;
ll dp[N][N];
void problem() {cin >> n >> s;dp[1][1] = 1;for (int i = 2; i <= n; ++i) {if (s[i - 2] == '<') {dp[i][1] = 0;for (int j = 2; j <= i; ++j) {dp[i][j] = (dp[i - 1][j - 1] + dp[i][j - 1]) % mod;}} else {dp[i][i] = 0;for (int j = i - 1; j; --j) {dp[i][j] = (dp[i - 1][j] + dp[i][j + 1]) % mod;}}}ll ans = 0;for (int i = 1; i <= n; ++i) {ans = (ans + dp[n][i]) % mod;}cout << ans << endl;
}
U - Grouping
- 状压dp
- 本题对于状压刚入门的童鞋可能比较难,但通过这题只要记住状压是怎么枚举所有子集的便可
- 这里写一遍,还要注意,枚举的复杂度为 O(3n3^n3n)
for (int s1 = s; s1; s1 = (s1 - 1) & s) {int s2 = s ^ s1;// s1 与 s2 互为 s 的补集
}
- 本题要的是真子集,所以要改改
void problem() {int n = IO(), m = 1 << n;for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {a[i][j] = IO();}}for (int i = 1; i < m; ++i) {for (int j = 0; j < n; ++j) {if (((i >> j) & 1) == 0) continue;for (int k = j + 1; k < n; ++k) {if (((i >> k) & 1) == 0) continue;dp[i] += a[j][k];}}for (int j = i & (i - 1); j; j = (j - 1) & i) {dp[i] = max(dp[i], dp[j] + dp[j ^ i]);}}print(dp[m - 1]);
}
V - Subtree
- 换根dp
- 换根过程中,考虑到模数可能不为质数,所以要记录每颗子树的前后缀积,
void dfs(int u, int fa) {dp[u] = 1;pre[u].push_back(1);for (int v : edg[u]) {if (v == fa) continue;dfs(v, u);dp[u] = dp[u] * (1 + dp[v]) % m;pre[u].pb(dp[u]);}for (int i = (int)edg[u].size() - 1; i >= 0; --i) {int v = edg[u][i];if (v == fa) continue;ll tmp = (dp[v] + 1) % m;if (last[u].size()) tmp = last[u].back() * tmp % m;;last[u].pb(tmp);}reverse(all(last[u]));last[u].pb(1);
}void solve(int u, int fa, ll sub) {int indx_pre = 0, indx_last = 1;for (int v : edg[u]) {if (v == fa) continue;ll tmp = pre[u][indx_pre++] * last[u][indx_last++] % m;// printf("%d %d\n", u, tmp);f[v] = (sub * tmp % m + 1) * dp[v] % m;solve(v, u, (tmp * sub + 1) % m);}
}void problem() {n = IO(), m = IO();for (int i = 1; i < n; ++i) {int u = IO(), v = IO();edg[u].pb(v), edg[v].pb(u);}dfs(1, 0);f[1] = dp[1];solve(1, 0, 1);for (int i = 1; i <= n; ++i) print(f[i], '\n');
}
W - Intervals
- 线段树优化dp
- 这题有点意思,一开始没发现要数据结构优化,怎么想都觉得是一个O(n3n^3n3)的dp,后来发现可以用线段树优化成O(nlognnlognnlogn)
- 设
dp[i]
表示前i
长度的字符串中,第i
个位置必填 1 的最大价值 - 然后先看转移方程,设结构体
p
为题中的区间
dp[i]=dp[j]+p[k].adp[i] = dp[j] + p[k].a dp[i]=dp[j]+p[k].a
其中0<=j<i其中 0 <= j < i 其中0<=j<i
并且j<p[k].l≤i并且 j < p[k].l \le i 并且j<p[k].l≤i
且p[k].r≥i且p[k].r \ge i 且p[k].r≥i
- 不难发现,暴力的话,这样转移是 O(n3n^3n3)
- 但我们会发现对于一个
p[k].l <= i
的区间,所有j < p[k].l
的dp[j]
都必须要加上这个区间,然后就很容易想到将所有区间按左端点升序排序,然后线段树进行转移便可,让区间[0, p[k].l - 1]
的所有dp值加上p[k].a
, 记得加完要减回去 - 看代码简单易懂
#define ls node << 1
#define rs (ls) | 1
struct pii {int x, y, v;bool operator< (const pii& j) {return x != j.x ? x < j.x : y < j.y;}
};
struct nodes{ll maxv, lazy;
};const int N = 1e5 + 10, M = 1e6 + 10;
ll dp[M << 1];
pii q[N << 1];
nodes seg[N << 3];
vector<int> pos[N << 1];void push_down(int node) {ll &tmp = seg[node].lazy;if (!tmp) return;seg[ls].lazy += tmp;seg[rs].lazy += tmp;seg[ls].maxv += tmp;seg[rs].maxv += tmp;tmp = 0;assert(seg[node].lazy == 0);
}
void update_range(int ql, int qr, ll v, int l, int r, int node = 1) {if (ql <= l && r <= qr) {seg[node].lazy += v;seg[node].maxv += v;return;}push_down(node);int mid = (l + r) >> 1;if (ql <= mid) update_range(ql, qr, v, l, mid, ls);if (qr > mid) update_range(ql, qr, v, mid + 1, r, rs);seg[node].maxv = max(seg[ls].maxv, seg[rs].maxv);
}
void update_pos(int indx, ll v, int l, int r, int node = 1) {if (l == r) {seg[node].maxv = v;return;}push_down(node);int mid = (l + r) >> 1;if (indx <= mid) update_pos(indx, v, l, mid, ls);else update_pos(indx, v, mid + 1, r, rs);seg[node].maxv = max(seg[ls].maxv, seg[rs].maxv);
}
ll query_range(int ql, int qr, int l, int r, int node = 1) {if (ql <= l && r <= qr) return seg[node].maxv;push_down(node);int mid = (l + r) >> 1;ll ret = -llf;if (ql <= mid) ret = query_range(ql, qr, l, mid, ls);if (qr > mid) ret = max(ret, query_range(ql, qr, mid + 1, r, rs));return ret;
}void problem() {int n = IO(), m = IO();for (int i = 0; i < m; ++i) {q[i].x = IO(), q[i].y = IO(), q[i].v = IO();}sort(q, q + m);for (int i = 0; i < m; ++i) pos[q[i].y].push_back(i);ll ans = 0;for (int i = 1, j = 0; i <= n; ++i) {while (j < m && q[j].x <= i) {update_range(0, q[j].x - 1, q[j].v, 0, n);j += 1;}dp[i] = query_range(0, i - 1, 0, n);update_pos(i, dp[i], 0, n);for (int p : pos[i]) update_range(0, q[p].x - 1, -q[p].v, 0, n);ans = max(ans, dp[i]);}print(ans);
}
X - Tower
- 贪心+01背包
- dp前的贪心排序要大胆地猜,然后证明
- 用领项交换法的思路去思考,当前决策相邻的两个物品 wi,siw_i, s_iwi,si 和 wj,sjw_j,s_jwj,sj ,前面物品(假设合法)的重量为 XXX,若以 iii 在 jjj 上的顺序放的话 jjj 物品还能承受的重量为 sj−X−wis_j - X - w_isj−X−wi,反之 iii 物品还能承受的重量为 si−X−wjs_i - X - w_jsi−X−wj,我们当然是想让 后面的更能承受重量,固 sj−X−wi>si−X−wjs_j - X - w_i > s_i - X - w_jsj−X−wi>si−X−wj,化简,我们就能发现只需要按 si+wi<sj+wjs_i + w_i < s_j + w_jsi+wi<sj+wj 排序,最后01背包找答案便可
struct pii {int w, s;ll v;bool operator< (const pii& j) {return w + s < j.w + j.s;}
};
pii p[1010];
ll dp[20010];
void problem() {int n = IO();for (int i = 0; i < n; ++i) {p[i].w = IO(), p[i].s = IO(), p[i].v = IO();}sort(p, p + n);for (int i = 0; i < n; ++i) {for (int j = p[i].s + p[i].w; j >= p[i].w; --j) {dp[j] = max(dp[j], dp[j - p[i].w] + p[i].v);}}print(*max_element(dp, dp + 20001));
}
Y - Grid 2
- 数学+dp
- 显然不能直接dp,会超时
- 如果是n*m的格子,无障碍的话方案数就是 Cn+m−2n−1C^{n - 1}_{n + m - 2}Cn+m−2n−1
- 找突破口,发现障碍的点不多
- 设
dp[i]
表示从(1, 1)
到第i
个障碍的方案数,下面是转移 - 设第
j
个障碍点在第i
个障碍点的左上方,再设从第j
个障碍点到第i
个障碍点形成的格子是 x * y 的,则
dp[i]+=dp[j]∗Cx+y−2x−1dp[i] += dp[j] * C^{x - 1}_{x + y - 2} dp[i]+=dp[j]∗Cx+y−2x−1
- 最后的答案便是将所有点往
(n, m)
点转移得到的答案 - 直接看代码,注意转移的顺序
ll powf(ll a, ll b, ll p, ll res = 1) {for (a %= p; b; b >>= 1, a = a * a % p) if (b & 1) res = res * a % p;return res;
}
struct pii {int x, y;bool operator< (const pii& j) {return x != j.x ? x < j.x : y < j.y;}
};ll dp[N], f[N * 2], inv[N * 2];
pii p[3010];ll comb(int n, int m) {if (n < m) return 0;return f[n] * inv[m] % mod * inv[n - m] % mod;
}
void problem() {f[0] = inv[0] = 1;for (int i = 1; i <= 200000; ++i) {f[i] = f[i - 1] * i % mod;inv[i] = powf(f[i], mod - 2, mod);}int h = IO(), w = IO(), n = IO();for (int i = 0; i < n; ++i) {p[i].x = IO(), p[i].y = IO();}sort(p, p + n);for (int i = 0; i < n; ++i) {dp[i] = comb(p[i].x + p[i].y - 2, p[i].x - 1);}for (int i = 0; i < n; ++i) {for (int j = 0; j < i; ++j) {if (p[j].x <= p[i].x && p[j].y <= p[i].y) {int x = p[i].x - p[j].x + 1, y = p[i].y - p[j].y + 1;dp[i] = dp[i] - dp[j] * comb(x + y - 2, x - 1) % mod;dp[i] = (dp[i] + mod) % mod;}}}ll ans = comb(h + w - 2, h - 1);for (int i = 0; i < n; ++i) {int x = h - p[i].x + 1, y = w - p[i].y + 1;ans = ans - dp[i] * comb(x + y - 2, x - 1) % mod;ans = (ans + mod) % mod;}print(ans);
}
Z - Frog 3
- 斜率优化的dp
- 当他写出 (hj−hi)2+C(h_j - h_i)^2 + C(hj−hi)2+C 的时候,用膝盖想都能猜到是要斜率优化了,再加上h数组还递增,好家伙,二分都省了
const int N = 1e5 + 10, M = 1e6 + 10;
ll a[M * 2], dp[M * 2], q[M * 2];
ll pow2(ll x) { return x * x; }
ll gety(int i) { return dp[i] + pow2(a[i]); }
ll getx(int i) { return a[i]; }int push_check(int j, int k, int i) {return gety(k) - gety(j) < 2 * a[i] * (getx(k) - getx(j));
}
int pop_check(int j, int k, int i) {return (gety(k) - gety(j)) * (getx(i) - getx(k)) > (gety(i) - gety(k)) * (getx(k) - getx(j));
}void problem() {int n = IO();ll m = IO();fill_w(a + 1, n);dp[1] = 0;int l = 0, r = -1;q[++r] = 1;for (int i = 2; i <= n; ++i) {while (l < r && push_check(q[l], q[l + 1], i)) ++l;dp[i] = dp[q[l]] + pow2(a[i] - a[q[l]]) + m;while (l < r && pop_check(q[r - 1], q[r], i)) --r;q[++r] = i;}print(dp[n]);
}
静态博客地址:https://haofish.github.io/2021/08/%E7%8B%82%E8%A1%A5dp-atcoder-educational-dp-contest%E5%85%A8/
狂补dp Atcoder Educational DP Contest(全)相关推荐
- Atcoder Educational DP Contest 题解
A - Frog 1/B - Frog 2 入门... 1 #include<cstdio> 2 #define abs(a) ((a)>=0?(a):(-(a))) 3 #defi ...
- [AtCoder Educational DP Contest] V - Subtree(树形dp + 前缀积/后缀积)
problem luogu 给一棵树,对每一个节点染成黑色或白色. 对于每一个节点,求强制把这个节点染成黑色的情况下,所有的黑色节点组成一个联通块的染色方案数,答案对 MMM 取模. 1≤n≤1e5, ...
- [AtCoder Educational DP Contest] J - Sushi(期望dp)
problem luogu 现有N(1≤N≤300)N(1 ≤ N ≤ 300)N(1≤N≤300) 个盘子,编号为1,2,3,-,N1,2,3,-,N1,2,3,-,N. 第 iii个盘中放有 ai ...
- Educational DP Contest
哎 期中又考炸了 dp是真的需要好好写一写了 问会长要了个dp的题单 确实又很多不会的地方 简单的就不记录了 主要记录自己的问题 题单链接 https://atcoder.jp/contests/dp ...
- Educational DP Contest U - Grouping 状压dp
传送门 题意: 给你nnn个物品,让你将其分成任意组,在同一个组内的i,ji,ji,j会获得ai,ja_{i,j}ai,j的收益,让你选择一种分组方案使得收益最大. 1≤n≤16,∣ai,j∣≤1e ...
- 湖南多校补题 状压dp
C - Greetings! 题目链接 题目大意 给n种信,每种信wi,hi,pi 分别代表长.宽.数量. 让选择k种信封来装这些信,每个信封里装一个. 如果把w,h的信装到了x,y的信封里,那么造成 ...
- 【细节很多的dp】Educational Codeforces Round 133 (Rated for Div. 2) C. Robot in a Hallway
参考题解 题意: 有一个 2 2 2 行, m m m 列的方格,初始在 ( 1 , 1 ) (1,1) (1,1),每个格子有一个开放时间,开放时间后才能到达,每个格子只能被到达一次,你可以任意选择 ...
- Atcoder Keyence Programming Contest 2020 D - Swap and Flip
Atcoder Keyence Programming Contest 2020 D - Swap and Flip 题目描述 Solution 写了一个简单的O(2nn(n+w))O(2^nn(n+ ...
- 【BZOJ】1076 [SCOI2008]奖励关 期望DP+状压DP
[题意]n种宝物,k关游戏,每关游戏给出一种宝物,可捡可不捡.每种宝物有一个价值(有负数).每个宝物有前提宝物列表,必须在前面的关卡取得列表宝物才能捡起这个宝物,求期望收益.k<=100,n&l ...
最新文章
- linux 开机启动 自启动 设置
- spring核心之AOP学习总结二
- 如何考查自己的测试水平?
- phpcmsV9 添加内容:如何“增加复选框、下拉菜单”(含案例、截图)- 教程篇
- 有关LinkedList常用方法的源码解析
- Ubuntu16.04 下SU画图,批量和单个
- vue的route和router的区别
- 软件测试——闭着眼睛测试软件
- Pure Storage到底是一家怎样的企业?
- sql 安装程序文件_【病毒文件分析】MedusaLocker勒索病毒,小心全网被加密
- 第一款无代码应用平台搭建的设备管理系统
- 美国软件是如何最终装备在中国攻击直升机上的(二)
- 工频逆变器有哪些应用?工频逆变器、高频逆变器有何区别?
- 阿里云-对象存储OSS
- Typhoon-v1.02 靶机入侵
- html5 讯飞离线语音包,讯飞输入法离线语音怎么用?讯飞输入法离线语音开启方法...
- 第十一章 曲面积分与曲线积分(同济高等数学A)
- windows下的python安装scrapy
- Golang 必知必会Go Mod命令
- 联想Lenovo G450过热死机解决办法