dp 百题计划 (11 / 100)
P3205 [HNOI2010]合唱队
区间 dp,考虑 表示让 这一区间符合并且最后是从左 / 右 进来的。
转移的话,考虑 这段区间,可能是 和 这两段区间转移来的。
而这俩段区间都可能是从左边进来的或者从右边进来的,符合条件就转移即可。
int n, a[N], dp[1020][1020][2]; signed main() { n = read(); for(int i = 1; i <= n; i++) a[i] = read(); for(int i = 1; i <= n; i++) dp[i][i][0] = 1; for(int len = 2; len <= n; len++) { for(int l = 1; l <= n - len + 1; l++) { int r = l + len - 1; if(a[l] < a[l + 1]) dp[l][r][0] = (dp[l][r][0] + dp[l + 1][r][0]) % mod; if(a[l] < a[r]) dp[l][r][0] = (dp[l][r][0] + dp[l + 1][r][1]) % mod; if(a[r] > a[r - 1]) dp[l][r][1] = (dp[l][r][1] + dp[l][r - 1][1]) % mod; if(a[r] > a[l]) dp[l][r][1] = (dp[l][r][1] + dp[l][r - 1][0]) % mod; } } printf("%d\n", (dp[1][n][1] + dp[1][n][0]) % mod); return 0; }
P4170 [CQOI2007]涂色
区间dp, 考虑 表示将 涂完的最小次数。
如果说 ,答案明显是 。
如果说 可以少涂一次 或者少涂一次 。
所以这部分的转移方程是 :
如果说 ,枚举端点。
char s[N]; int n, dp[100][100]; signed main() { 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, r = l + len - 1; l <= n - len + 1; l++, r++) { 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]); } } printf("%d\n", dp[1][n]); return 0; }
P3842 [TJOI2007]线段
设 表示走到第 条线段, 是在做左端点结束的, 是在右端点结束的。
分类讨论,从上一行转移,注意要取绝对值。
#include <bits/stdc++.h> using namespace std; const int MAXN = 2e4 + 10; inline int read() { int s = 0, f = 0; char ch = getchar(); for(; !isdigit(ch); ch = getchar()) f |= (ch == '-'); for(; isdigit(ch); ch = getchar()) s = (s << 1) + (s << 3) + (ch ^ '0'); return f ? -s : s; } int n, dp[MAXN][2], l[MAXN], r[MAXN]; int main() { n = read(); for(int i = 1; i <= n; i++) l[i] = read(), r[i] = read(); dp[1][0] = r[1] + (r[1] - l[1] + 1), dp[1][1] = r[1] - 1; for(int i = 2; i <= n; i++) { dp[i][0] = min(dp[i - 1][0] + abs(r[i] - l[i - 1]) + abs(r[i] - l[i] + 1), dp[i - 1][1] + abs(r[i] - r[i - 1]) + abs(r[i] - l[i] + 1)); dp[i][1] = min(dp[i - 1][0] + abs(l[i] - l[i - 1]) + abs(r[i] - l[i] + 1), dp[i - 1][1] + abs(r[i - 1] - l[i]) + abs(r[i] - l[i] + 1)); } return printf("%d\n", min(dp[n][0] + abs(n - l[n]), dp[n][1] + abs(n - r[n]))), 0; }
ABC 267 D
一眼 dp,设 为到第 个位置,选了 个。
所以
十分显然,但是注意初始化。
int n, a[N], m; int dp[2020][2020]; signed main() { n = read(), m = read(); for (int i = 0; i <= n; i++) for (int j = 1; j <= n; j++) dp[i][j] = -inf; for (int i = 1; i <= n; i++) a[i] = read(); for (int i = 1; i <= n; i++) { for (int j = 1; j <= min(i, m); j++) { dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] + a[i] * j); } } int Max = -inf; for (int i = 1; i <= n; i++) Max = max(Max, dp[i][m]); cout << Max; return (0 - 0); }
P1107 [BJWC2008]雷涛的小猫
比较一眼的 dp 吧。
设 表示到了第 i 层高度,第 j 颗树的最大柿子数量。
然后可以用一个数组来维护最大值,就做到了 的复杂度。
/** * author: TLE_Automation * creater: 2022.10.14 **/ #include<cmath> #include<queue> #include<cstdio> #include<vector> #include<bitset> #include<cstring> #include<iostream> #include<algorithm> #define gc getchar using namespace std; typedef long long ll; const int N = 1e6 + 10; const int Maxn = 2e3 + 10; const int mod = 998244353; const ll inf = 0x3f3f3f3f3f3f3f3f; #define debug cout << "i ak ioi" << "\n" inline void print(int x) {if (x < 0) putchar('-'), x = -x; if(x > 9) print(x / 10); putchar(x % 10 + '0');} inline char readchar() {static char buf[100000], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;} inline int read() { int res = 0, f = 0; char ch = gc();for (; !isdigit(ch); ch = gc()) f |= (ch == '-'); for (; isdigit(ch); ch = gc()) res = (res << 1) + (res << 3) + (ch ^ '0'); return f ? -res : res;} int n, h, del, a[Maxn][Maxn], dp[Maxn][Maxn], Max[Maxn]; /* dp[i][j] 表示到了第 i 层高度,第 j 颗树的最大柿子数量 */ signed main() { n = read(), h = read(), del = read(); for(int i = 1; i <= n; i++) { int x = read(); for(int j = 1; j <= x; j++) a[read()][i]++; } for(int i = 1; i <= h; i++) { for(int j = 1; j <= n; j++) { if(i > del) dp[i][j] = std::max(dp[i - 1][j], Max[i - del]) + a[i][j]; else dp[i][j] = dp[i - 1][j] + a[i][j]; Max[i] = std::max(Max[i], dp[i][j]); } } int ans = 0; for(int i = 1; i <= n; i++) ans = std::max(ans, dp[h][i]); cout << ans; return (0 - 0); }
P1833 樱花
多重背包板子。
二进制分组优化一下就行了, 然后如果说可以无限选的话,就设 k 为一个较大的数就行了。
/** * author: TLE_Automation * creater: 2022.10.14 **/ #include<cmath> #include<queue> #include<cstdio> #include<vector> #include<bitset> #include<cstring> #include<iostream> #include<algorithm> #define gc getchar using namespace std; typedef long long ll; const int N = 1e6 + 10; const int mod = 998244353; const ll inf = 0x3f3f3f3f3f3f3f3f; #define debug cout << "i ak ioi" << "\n" inline void print(int x) {if (x < 0) putchar('-'), x = -x; if(x > 9) print(x / 10); putchar(x % 10 + '0');} inline char readchar() {static char buf[100000], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;} inline int read() { int res = 0, f = 0; char ch = gc();for (; !isdigit(ch); ch = gc()) f |= (ch == '-'); for (; isdigit(ch); ch = gc()) res = (res << 1) + (res << 3) + (ch ^ '0'); return f ? -res : res;} int n, V, val[N], tim[N], idx = 0, dp[N]; int h1, h2, m1, m2; signed main() { cin >> h1; gc(); cin >> m1; cin >> h2; gc(); cin >> m2; if(m2 >= m1) V = (h2 - h1) * 60 + (m2 - m1); else h2--, m2 += 60, V = (h2 - h1) *60 + (m2 - m1); n = read(); for(int i = 1; i <= n; i++) { int vv = read(), ww = read(), k = read(); if(!k) k = 1e6; for(int j = 1; j <= k; j <<= 1) { val[++idx] = ww * j, tim[idx] = vv * j; k -= j; } val[++idx] = ww * k, tim[idx] = vv * k; } for(int i = 1; i <= idx; i++) { for(int j = V; j >= tim[i]; j--) { dp[j] = max(dp[j], dp[j - tim[i]] + val[i]); } } cout << dp[V] << "\n"; return (0 - 0); }
P1064 [NOIP2006 提高组] 金明的预算方案
有依赖问题的背包dp。
分类讨论。
- 不买
- 只卖主件
- 主件+第一个附件
- 主件+第二个附件
- 主件+第一个附件+第二个附件
转移的时候还是 背包转移就行了。
/** * author: TLE_Automation * creater: 2022.10.14 **/ #include<cmath> #include<queue> #include<cstdio> #include<vector> #include<bitset> #include<cstring> #include<iostream> #include<algorithm> #define mp make_pair #define pb push_back #define gc getchar #define int long long using namespace std; typedef long long ll; const int N = 1e6 + 10; const int mod = 998244353; const ll inf = 0x3f3f3f3f3f3f3f3f; #define debug cout << "i ak ioi" << "\n" inline void print(int x) {if (x < 0) putchar('-'), x = -x; if(x > 9) print(x / 10); putchar(x % 10 + '0');} inline char readchar() {static char buf[100000], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;} inline int read() { int res = 0, f = 0; char ch = gc();for (; !isdigit(ch); ch = gc()) f |= (ch == '-'); for (; isdigit(ch); ch = gc()) res = (res << 1) + (res << 3) + (ch ^ '0'); return f ? -res : res;} int V, n, dp[N]; int cnt[N]; pair<int, int> a[N][5]; // first->tim second->val /* 1.不买 2.只卖主件 3.主件+1 4.主件+2 5.主件+1,2 */ #define Val(x,y) a[x][y].second #define Tim(x,y) a[x][y].first signed main() { V = read(), n = read(); for(int i = 1; i <= n; i++) { int tim = read(), val = read(), op = read(); if(!op) a[i][0].second = val * tim, a[i][0].first = tim; else a[op][++cnt[op]].second = val * tim, a[op][cnt[op]].first = tim; } for(int i = 1; i <= n; i++) { for(int j = V; j >= a[i][0].first; j--) { if(j >= Tim(i, 0)) dp[j] = max(dp[j], dp[j - Tim(i, 0)] + Val(i, 0)); if(j >= Tim(i, 0) + Tim(i, 1)) dp[j] = max(dp[j], dp[j - Tim(i, 0) - Tim(i, 1)] + Val(i, 0) + Val(i, 1)); if(j >= Tim(i, 0) + Tim(i, 2)) dp[j] = max(dp[j], dp[j - Tim(i, 0) - Tim(i, 2)] + Val(i, 0) + Val(i, 2)); if(j >= Tim(i, 0) + Tim(i, 1) + Tim(i, 2)) dp[j] = max(dp[j], dp[j - Tim(i, 0) - Tim(i, 1) - Tim(i, 2)] + Val(i, 0) + Val(i, 1) + Val(i, 2)); } } cout << dp[V]; return (0 - 0); }
P2340 [USACO03FALL]Cow Exhibition G
感觉是道比较精妙的分类讨论背包题?
我们将智商看做是体积,情商看做价值。
设 表示到了第 头牛,智商为 的最大情商数。
转移方程是 。
这样做是会 MLE 的,我们考虑压掉一维,但是这题是有负数的,所以要将整个 dp 数组右移可能的最小智商和。
然后分类讨论来压掉一维:
- 当 时,,我们压掉一维是想让转移 的时候,
和 都未被转移过,所以倒叙枚举。
- 当 时, 正序枚举能保证用到的状态都是未被转移过的,也就是上一层的状态。
/** * author: TLE_Automation * creater: 2022.10.19 **/ #include<cmath> #include<queue> #include<cstdio> #include<vector> #include<bitset> #include<cstring> #include<iostream> #include<algorithm> #define gc getchar #define Tim(x) a[x].first #define Val(x) a[x].second using namespace std; typedef long long ll; const int qwq = 4e5; const int N = 1e6 + 10; const int mod = 998244353; const ll inf = 0x3f3f3f3f3f3f3f3f; #define debug cout << "i ak ioi" << "\n" inline void print(int x) {if (x < 0) putchar('-'), x = -x; if(x > 9) print(x / 10); putchar(x % 10 + '0');} inline char readchar() {static char buf[100000], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;} inline int read() { int res = 0, f = 0; char ch = gc();for (; !isdigit(ch); ch = gc()) f |= (ch == '-'); for (; isdigit(ch); ch = gc()) res = (res << 1) + (res << 3) + (ch ^ '0'); return f ? -res : res;} /* 因为有负数,考虑将整个数组右移 1000*400 dp_j = dp_{j - v_i} + w_i dp_j 表示智商为 j 的最大情商 */ int n, dp[N << 1]; pair <int, int> a[N]; signed main() { n = read(); memset(dp, -0x3f, sizeof dp); dp[qwq] = 0; for(int i = 1; i <= n; i++) a[i].first = read(), a[i].second = read(); for(int i = 1; i <= n; i++) { if(Tim(i) >= 0) { for(int j = qwq << 1; j >= Tim(i); j--) dp[j] = max(dp[j], dp[j - Tim(i)] + Val(i)); } else { for(int j = 0; j <= (qwq << 1) - Tim(i) ; j++) dp[j] = max(dp[j], dp[j - Tim(i)] + Val(i)); } } int ans = -0x3f3f3f3f; for(int i = qwq; i <= qwq << 1; i++) { if(dp[i] >= 0) ans = max(dp[i] + i - qwq, ans); } cout << ans; return (0 - 0); }
P4059 [Code+#1]找爸爸
因为 的性质可以看成是:
-
如果是第一个空格,就 。
-
不是第一个空格,就 。
我们设 分别表示匹配到了上面串的第 个位置,下面串的第 个位置,两个都没有空格,上面的有空格,下面的有空格。
转移的话就从对应的状态转移来。
注意一开始赋值极小值,因为空格的贡献是负的。
#include <bits/stdc++.h> typedef long long ll; using namespace std; constexpr int Maxn = 3e3 + 10; constexpr ll inf = 0x3f3f3f3f3f3f3f3f; ll dp[Maxn][Maxn][3]; char s[Maxn], t[Maxn]; int val[5][5], n, m, A, B; inline int getpos(char s) { if (s == 'A') return 1; else if (s == 'T') return 2; else if (s == 'G') return 3;u else if (s == 'C') return 4; else return 114514; } int main() { scanf("%s %s", s + 1, t + 1); n = strlen(s + 1), m = strlen(t + 1); for (int i = 1; i <= 4; i++) for (int j = 1; j <= 4; j++) scanf("%d", &val[i][j]); scanf("%d %d", &A, &B); memset(dp, -0x3f, sizeof dp); dp[0][0][0] = 0; for (int i = 0; i <= n; i++) { for (int j = 0; j <= m; j++) { if(i && j) dp[i][j][0] = std::max(dp[i - 1][j - 1][0], std::max(dp[i - 1][j - 1][1], dp[i - 1][j - 1][2])) + val[getpos(s[i])][getpos(t[j])]; if(j) dp[i][j][1] = std::max(dp[i][j - 1][0] - A, std::max(dp[i][j - 1][1] - B, dp[i][j - 1][2] - A)); if(i) dp[i][j][2] = std::max(dp[i - 1][j][0] - A, std::max(dp[i - 1][j][1] - A, dp[i - 1][j][2] - B)); } } cout << std::max(dp[n][m][0], std::max(dp[n][m][1], dp[n][m][2])); return 0; }
P3146 [USACO16OPEN]248 G
设 为合并区间 的最大值。
#include<bits/stdc++.h> using namespace std; int dp[500][500], n, a[500]; int main() { cin >> n; for(int i = 1; i <= n; i++) cin >> a[i]; memset(dp, -0x3f, sizeof dp); for(int i = 1; i <= n; i++) dp[i][i] = a[i]; for(int len = 2; len <= n; len++) { for(int l = 1, r; l + len - 1 <= n; l++) { r = l + len - 1; for(int k = l; k <= r; k++) if(dp[l][k] == dp[k + 1][r]) dp[l][r] = max(dp[l][r], dp[l][k] + 1); } } int ans = -0x3f3f3f3f; for(int l = 1; l <= n; l++) for(int r = 1; r <= n; r++) ans = max(ans, dp[l][r]); cout << ans; return 0; }
P3147 [USACO16OPEN]262144 P
上一题的加强版,设 表示以 为左端点合并出 的右端点的右侧。
#include<cstdio> int dp[60][300010], n, ans; int main() { scanf("%d", &n); for(int i = 1, x; i <= n; i++) scanf("%d", &x), dp[x][i] = i + 1; for(int i = 2; i <= 58; i++) for(int j = 1; j <= n; j++) { if(!dp[i][j]) dp[i][j] = dp[i - 1][dp[i - 1][j]]; if(dp[i][j]) ans = i; } printf("%d", ans); }
本文作者:TLE_Automation
本文链接:https://www.cnblogs.com/tttttttle/p/16602154.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!