区间DP小组题解
注:在这里放一份,方便看,如有侵权线下联系
区间 DP
A - CF1509C The Sports Festival by zjy
发现
将
设
对于
这样做复杂度是
发现可以从外向内转移,也就是倒着选,
转移:
答案:
sort (a + 1, a + n + 1);
memset (dp, 0x3f, sizeof (dp));
dp[1][n] = a[n] - a[1];
for (int i = 1; i <= n; i++)
for (int j = n; j >= i; j--)
if (i != 1 || j != n) dp[i][j] = min (dp[i][j + 1], dp[i - 1][j]) + a[j] - a[i];
for (int i = 1; i <= n; i++) ans = min (ans, dp[i][i]);
cout << ans;
B - CF1728D Letter Picking by shx
考虑一个区间
-
先手必胜条件:
-
先手选
必胜条件是后手不管选
还是 ,都是先手获胜。 -
先手选
必胜条件是后手不管选
还是 ,都是先手获胜。
-
-
平局条件:
-
先手选
必胜条件是后手不管选
还是 ,都是平局。 -
先手选
必胜条件是后手不管选
还是 ,都是平局。
-
int Fn(int l, int r) {
if (l > r) return 0;
if (mp.count({l, r})) return mp[{l, r}];
int ll = Fn(l + 2, r), lr = Fn(l + 1, r - 1), rr = Fn(l, r - 2);
if ((ll == 1 || (ll == 0 && s[l] < s[l + 1])) && (lr == 1 || (lr == 0 && s[l] < s[r])))
return mp[{l, r}] = 1; // 先手选 l, 后手必败
if ((rr == 1 || (rr == 0 && s[r] < s[r - 1])) && (lr == 1 || (lr == 0 && s[r] < s[l])))
return mp[{l, r}] = 1; // 先手选 r, 后手必败
if ((ll == 1 || (ll == 0 && s[l] <= s[l + 1])) && (lr == 1 || (lr == 0 && s[l] <= s[r])))
return mp[{l, r}] = 0; // 先手选 l, 后手必不赢
if ((rr == 1 || (rr == 0 && s[r] <= s[r - 1])) && (lr == 1 || (lr == 0 && s[r] <= s[l])))
return mp[{l, r}] = 0; // 先手选 r, 后手必不赢
return mp[{l, r}] = -1;
}
C - CF1666J Job Lookup by shx
其任意一个节点的左子树内所有节点编号都小于它,右子树内所有节点编号都大于它。所以一个区间是一个完整的子树。
考虑枚举一个区间的根
转移方程:
ll Calc(int sx, int fx, int sy, int fy) {
if (sx > fx || sy > fy) return 0;
return s[fx][fy] - s[fx][sy - 1] - s[sx - 1][fy] + s[sx - 1][sy - 1];
}
void Dfs(int l, int r, int p) {
if (l > r) return void();
if (l == r) return ans[l] = p, void();
ans[fa[l][r]] = p;
Dfs(l, fa[l][r] - 1, fa[l][r]), Dfs(fa[l][r] + 1, r, fa[l][r]);
}
for (int len = 2; len <= n; ++len) {
for (int l = 1, r; (r = l + len - 1) <= n; ++l) {
for (int k = l; k <= r; ++k) {
ll tmp = (k != l) * f[l][k - 1] + (k != r) * f[k + 1][r] +
Calc(l, k - 1, 1, l - 1) + Calc(l, k - 1, k, n) + Calc(k + 1, r, 1, k) + Calc(k + 1, r, r + 1, n);
if (f[l][r] > tmp) f[l][r] = tmp, fa[l][r] = k;
}
}
}
Dfs(1, n, 0);
D - [USACO04OPEN] Turning in Homework G by zjy
因为交作业不需要时间,所以交一个区间的作业的时候,一定是先交左端点或者先交右端点。
设
初始:
转移:从相邻区间的两个状态转移而来。
答案为
时间复杂度
sort (a + 1, a + n + 1, [] (Node a, Node b) { return a.x < b.x; });
memset (dp, 0x3f, sizeof (dp));
dp[1][n][0] = max (a[1].x, a[1].t);
dp[1][n][1] = max (a[n].x, a[n].t);
for (int i = 1; i <= n; ++i)
for (int j = n; j >= i; j--) {
if (i == 1 && j == n) continue;
dp[i][j][0] = max (a[i].t, min (dp[i - 1][j][0] + a[i].x - a[i - 1].x, dp[i][j + 1][1] + a[j + 1].x - a[i].x));
dp[i][j][1] = max (a[j].t, min (dp[i - 1][j][0] + a[j].x - a[i - 1].x, dp[i][j + 1][1] + a[j + 1].x - a[j].x));
}
for (int i = 1; i <= n; i++) ans = min (ans, min (dp[i][i][0], dp[i][i][1]) + abs (b - a[i].x));
cout << ans;
E - [CERC2014] Outer space invaders by shx
考虑如果对一个人进行 DP,发现由于左右端点不固定,不很好转移,考虑对时间 DP。时间显然可以离散化到
设
枚举对距离最大的敌人的攻击时间
令
如果区间不完全包含某个敌人 DP 值为
for (int len = 1; len <= w; ++len) {
for (int l = 1, r, p = 0; (r = l + len - 1) <= w; ++l) {
for (int i = 1; i <= n; ++i)
if (l <= a[i].l && a[i].r <= r && (a[i].d > a[p].d)) p = i;
if (!p) {
f[l][r] = 0;
} else {
f[l][r] = 1E18;
for (int k = a[p].l; k <= a[p].r; ++k) Chmin(f[l][r], f[l][k - 1] + f[k + 1][r]);
f[l][r] += a[p].d;
}
p = 0; // 记得清空 QWQ
}
}
printf("%lld\n", f[1][w]);
F - [USACO17JAN] Subsequence Reversal P by fyx
根据数据范围,不难想到 DP 状态应该是
先考虑当没有反转区间的操作时如何转移。
设
加上翻转操作后,我们思考其本质。翻转一个子序列可以理解为交换某几对数字的位置,这样的话相当于如果
由于区间 DP 按照区间从小到大的顺序,故可以保证这样的翻转满足题目条件,所以这道题就结束了。
cin >> n;
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i <= n; i++)
for (int l = 1; l <= a[i]; l++)
for (int r = a[i]; r <= 50; r++) dp[i][i][l][r] = 1;
for (int len = 2; len <= n; len++) {
for (int l = 1; l <= n - len + 1; l++) {
int r = l + len - 1;
for (int lenn = 1; lenn <= 50; lenn++) {
for (int L = 1; L <= 50 - lenn + 1; L++) {
int R = L + lenn - 1;
dp[l][r][L][R] = max(dp[l][r][L + 1][R], dp[l][r][L][R - 1]);
dp[l][r][L][R] = max(dp[l][r][L][R], dp[l + 1][r][L][R] + (a[l] == L));
dp[l][r][L][R] = max(dp[l][r][L][R], dp[l][r - 1][L][R] + (a[r] == R));
dp[l][r][L][R] = max(dp[l][r][L][R], dp[l + 1][r - 1][L][R] + (a[l] == R) + (a[r] == L));
}
}
}
}
cout << dp[1][n][1][50];
G - [春季测试 2023] 圣诞树 by shx
考虑对于:
A B
C D
这样的节点,一定不会是
考虑设
答案为
转移式子:
输出方案可以在记录一个
inline double Dis(int i, int j) {
return sqrt((a[i].x - a[j].x) * (a[i].x - a[j].x) + (a[i].y - a[j].y) * (a[i].y - a[j].y));
}
void Dfs(int l, int r, int op) {
if (l == r) return printf(" %d", a[l].id), void();
if (op) printf(" %d", a[r].id), Dfs(l, r - 1, p[l][r][op]);
else printf(" %d", a[l].id), Dfs(l + 1, r, p[l][r][op]);
}
int main() {
cin >> n;
for (int i = 1; i <= n; ++i) cin >> s[i].x >> s[i].y, s[i].id = i, ((s[i].y > maxy) && (maxy = s[i].y, k = i));
for (int i = 1; i <= n; ++i) a[(i - k + n) % n] = s[i]; a[n] = s[k];
for (int len = 2; len < n; ++len)
for (int l = 1, r; (r = l + len - 1) < n; ++l) {
f[l][r][0] = f[l][r][1] = 1E100;
if (f[l][r][0] > f[l + 1][r][0] + Dis(l + 1, l)) f[l][r][0] = f[l + 1][r][0] + Dis(l, l + 1), p[l][r][0] = 0;
if (f[l][r][0] > f[l + 1][r][1] + Dis(r, l)) f[l][r][0] = f[l + 1][r][1] + Dis(r, l), p[l][r][0] = 1;
if (f[l][r][1] > f[l][r - 1][0] + Dis(l, r)) f[l][r][1] = f[l][r - 1][0] + Dis(l, r), p[l][r][1] = 0;
if (f[l][r][1] > f[l][r - 1][1] + Dis(r - 1, r)) f[l][r][1] = f[l][r - 1][1] + Dis(r - 1, r), p[l][r][1] = 1;
}
cerr << min(f[1][n - 1][0] + Dis(1, n), f[1][n - 1][1] + Dis(n - 1, n)) << endl;
printf("%d", k), f[1][n - 1][0] + Dis(1, n) < f[1][n - 1][1] + Dis(n - 1, n) ? Dfs(1, n - 1, 0) : Dfs(1, n - 1, 1);
return 0;
}
J - [CQOI2007] 涂色 by fyx
仔细想想其实没有 dX 说的那么夸张[1]。
考虑一个结论:一定存在一种最优方案使得使得任意一次染色的区间一定是完全包含之前某一次染色区间或者与之前某一次染色区间完全不交且不与之前所有染色区间相交。
简单来说,如果我们当前的染色方案与之前某一次相交,那么我们完全可以缩短当前染色区间使得不交。
这样我们设
-
完全包含。此时一定有
,那么 可以由 和 转移过来。具体的,比如对于ABA
,它可以由AB
直接转移,即第一次染色时可以向右端点多染一次。这里可能会有疑问,也许这种染色方式会不满足上文条件。但其实不要紧,因为就算不满足我们也可以通过前文的转化方式使其满足条件。等价于转移时不需要完全满足上文条件。 -
完全不相交。我们在这里其实只需要考虑紧挨的情况,即类似
AABB
,因为不紧挨的情况可以由多个紧挨的情况转移过来。所以只需要枚举断点 ,合并累加两段答案即可。
scanf("%s", s + 1), n = strlen(s + 1), memset(dp, 0x3f, sizeof dp);
for (int i = 1; i <= n; i++) dp[i][i] = 1;
for (int len = 2; len <= n; len++) {
for (int l = 1; l <= n - len + 1; l++) {
int r = l + len - 1;
if (s[l] == s[r]) dp[l][r] = min(dp[l][r - 1], dp[l + 1][r]);
else for (int k = l; k <= r; k++) dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]);
}
}
cout << dp[1][n];
前提是放在区间 DP 的作业里。 ↩︎
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】