dp 百题计划 (11 / 100)
P3205 [HNOI2010]合唱队
区间 dp,考虑 \(dp[l][r][0 / 1]\) 表示让 \([l, r]\) 这一区间符合并且最后是从左 / 右 进来的。
转移的话,考虑 \([l, r]\) 这段区间,可能是 \([l, r - 1]\) 和 \([l + 1, r]\) 这两段区间转移来的。
而这俩段区间都可能是从左边进来的或者从右边进来的,符合条件就转移即可。
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, 考虑 \(dp[l][r]\) 表示将 \([l, r]\) 涂完的最小次数。
如果说 \(l = r\),答案明显是 \(1\)。
如果说 $s[l] == s[r](l\ne r) $ 可以少涂一次 \(l\) 或者少涂一次 \(r\)。
所以这部分的转移方程是 :
如果说 \(s[l] \ne s[r]\) ,枚举端点。
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]线段
设 \(dp_{i, 0 / 1 }\) 表示走到第 \(i\) 条线段,\(0\) 是在做左端点结束的,\(1\) 是在右端点结束的。
分类讨论,从上一行转移,注意要取绝对值。
#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,设 \(dp_{i, j}\) 为到第 \(i\) 个位置,选了 \(j\) 个。
所以 \(dp_{i, j} = \max( dp_{i-1,j,}dp_{i-1, j-1} + a[i] \times j)\)
十分显然,但是注意初始化。
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 吧。
设 $dp_{i, j} $ 表示到了第 i 层高度,第 j 颗树的最大柿子数量。
然后可以用一个数组来维护最大值,就做到了 \(\mathcal{O}(n^2)\) 的复杂度。
/**
* 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。
分类讨论。
- 不买
- 只卖主件
- 主件+第一个附件
- 主件+第二个附件
- 主件+第一个附件+第二个附件
转移的时候还是 \(01\) 背包转移就行了。
/**
* 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
感觉是道比较精妙的分类讨论背包题?
我们将智商看做是体积,情商看做价值。
设 \(dp_{i,j}\) 表示到了第 \(i\) 头牛,智商为 \(j\) 的最大情商数。
转移方程是 \(dp_{i,j} = \max\{dp_{i-1,j}, dp_{j-Tim_i}+Val_i\}\)。
这样做是会 MLE 的,我们考虑压掉一维,但是这题是有负数的,所以要将整个 dp 数组右移可能的最小智商和。
然后分类讨论来压掉一维:
- 当 \(Tim_i \ge 0\) 时,\(dp_{i,j} = \max\{dp_{i-1,j}, dp_{j-Tim_i}+Val_i\}\),我们压掉一维是想让转移 \(dp_j\) 的时候,
\(dp_j\) 和 \(dp_{j-Tim_i}\) 都未被转移过,所以倒叙枚举。
- 当 \(Tim_i < 0\) 时, 正序枚举能保证用到的状态都是未被转移过的,也就是上一层的状态。
/**
* 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]找爸爸
因为 \(g(k)\) 的性质可以看成是:
-
如果是第一个空格,就 \(-A\)。
-
不是第一个空格,就 \(-B\)。
我们设 \(dp[i][j][0/1/2]\) 分别表示匹配到了上面串的第 \(i\) 个位置,下面串的第 \(j\) 个位置,两个都没有空格,上面的有空格,下面的有空格。
转移的话就从对应的状态转移来。
注意一开始赋值极小值,因为空格的贡献是负的。
#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
设 \(dp_{l,r}\) 为合并区间 \([l, r]\) 的最大值。
#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
上一题的加强版,设 \(dp_{i,j}\) 表示以 \(j\) 为左端点合并出 \(i\) 的右端点的右侧。
#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);
}

dp 做题
浙公网安备 33010602011771号