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\)

所以这部分的转移方程是 :

\[dp[l][r] = \min(dp[l][r - 1], dp[l + 1][r]) \]

如果说 \(s[l] \ne s[r]\) ,枚举端点。

\[dp[l, r] = \min(dp[l][r], dp[l][k] + dp[k +1][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 颗树的最大柿子数量。

\[dp_{i,j} = \max\{dp_{i - 1, j},\max_{i=1}^ndp_{i-del,j}\} + a_{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\) 的右端点的右侧。

\[dp_{i,j} = dp_{{i-1},{dp_{i-1,j}}} \]

#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);	
}
posted @ 2022-08-19 15:29  TLE_Automation  阅读(44)  评论(1)    收藏  举报