区间dp
https://blog.csdn.net/my_sunshine26/article/details/77141398
https://blog.csdn.net/liuweiyuxiang/article/details/78827474
https://blog.csdn.net/xuechen_gemgirl/article/details/80830106
https://cn.vjudge.net/contest/224309#problem/A
String painter
题意:给出两长度相同的字符串,每次操作可以把第一个字符串的一段区间内的所有字母都换成同一个字母,现要将第一个字符串转换成第二个,求最少执行多少次操作
思路:用 dp[i][j] 来表示在区间 i~j 内操作的最少次数,先对字符串2进行处理,假设第 i 个字符需要操作一次,则有 dp[i][j]=dp[i+1][j]+1,在区间 i~j 内,若第 k 个字符与第 i 个相同,则可以将区间 i~j 分成两部分 dp[i][j]=min(dp[i+1][k]+dp[k+1][j]);
处理完第2个字符串后,用 sum[i] 记录字符串的区间 0~i 的喷刷次数,如果字符串1的 i 位置与字符串2的 i 位置相同,那么这个位置就不需要进行处理,即 sum[i]=sum[i-1],如果不同,就在新区间内分割开求最小值
#include <cstdio> #include <cmath> #include <queue> #include <cstring> #include <algorithm> using namespace std; #define mst(a,b) memset((a),(b),sizeof(a)) #define rush() int T;scanf("%d",&T);while(T--) typedef long long ll; const int maxn = 105; const ll mod = 1e9+7; const int INF = 0x3f3f3f3f; const double eps = 1e-9; char s1[maxn],s2[maxn]; int dp[maxn][maxn]; int ans[maxn]; int main() { while(~scanf("%s%s",s1+1,s2+1)) { int n=strlen(s1+1); mst(dp,0); for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) { dp[i][j]=j-i+1; } for(int j=1;j<=n;j++) for(int i=j;i>=1;i--) { dp[i][j]=dp[i+1][j]+1; for(int k=i+1;k<=j;k++) { if(s2[i]==s2[k]) //这样的话s2[i]可以跟左半区间一起刷到 { dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]); } } } for(int i=1;i<=n;i++) { ans[i]=dp[1][i]; if(s1[i]==s2[i]) { ans[i]=min(ans[i],ans[i-1]); } else for(int j=1;j<i;j++) { ans[i]=min(ans[i],ans[j]+dp[j+1][i]); //已得出的最优解+剩下的按空白串去刷 } } printf("%d\n",ans[n]); } return 0; }
Play Game
题意
Alice和Bob玩一个游戏,有两个长度为N的正整数数字序列,每次他们两个
只能从其中一个序列,选择两端中的一个拿走。他们都希望可以拿到尽量大
的数字之和,并且他们都足够聪明,每次都选择最优策略。Alice先选择,问
最终Alice拿到的数字总和是多少?
思路
这题应该算是区间dp吧,可以看一下这题的原型:
其他规则都一样,但是只有一个数字序列,也是每次只能拿左右两端的一个数字,问最终Alice拿多少? (这个可以去做uva-10891)
只有一行数字序列可以用f(i, j)表示数字序列还剩下区间[i,j]段时开始拿,最多可以拿多少数字
而这题只是变成了两行数字序列, 那么可以在上面的基础上,再增加两维
变成f(i, j, k, l), 表示第一个序列剩下区间[i,j],第二个序列剩下区间[k,l]的情况下开始拿,最多可以拿多少?
当面临状态f(i, j, k, l) 时,你有四种选择:
1. 选择第一行的最左边数字
2. 选择第一行的最右边数字
3. 选择第二行的最左边数字
4. 选择第二行的最右边数字
所以, f(i, j, k, l)可以由:
f(i+1, j, k, l)
f(i, j-1, k, l)
f(i, j, k+1, l)
f(i, j, k, l-1)
这四种状态转移而来,
假设当前状态是Alice要选择,那么上一个状态就是Bob选择的最大值,
为了要让Alice的最终和最大,那么就要选择上面四种状态最小的一个转,
设sum(i, j, k, l) 表示地一个序列[i,j]段之和与第二个序列的[k,l]段之和的和。
sum(i, j, k, l) - 上一次Bob拿的值就等于Alice能拿到的值
f(i, j, k, l) = sum(i, j, k, l) -
min{
f(i+1, j, k, l)
f(i, j-1, k, l)
f(i, j, k+1, l)
f(i, j, k, l-1)
}
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N = 22; int a[N], b[N]; int sum_a[N], sum_b[N]; int dp[N][N][N][N]; int dfs(int ax, int ay, int bx, int by) { if(ax > ay && bx > by) return 0; if(dp[ax][ay][bx][by] != -1) return dp[ax][ay][bx][by]; int ans = 0, sum = 0; if(ax <= ay) sum += sum_a[ay] - sum_a[ax - 1]; if(bx <= by) sum += sum_b[by] - sum_b[bx - 1]; if(ax <= ay) ans = max(ans, sum - dfs(ax + 1, ay, bx, by)); if(ax <= ay) ans = max(ans, sum - dfs(ax, ay - 1, bx, by)); if(bx <= by) ans = max(ans, sum - dfs(ax, ay, bx + 1, by)); if(bx <= by) ans = max(ans, sum - dfs(ax, ay, bx, by - 1)); return dp[ax][ay][bx][by] = ans; } int main() { int t, n; scanf("%d", &t); while(t--) { scanf("%d", &n); for(int i = 1; i <= n; i++) { scanf("%d", &a[i]); sum_a[i] = sum_a[i - 1] + a[i]; } for(int i = 1; i <= n; i++) { scanf("%d", &b[i]); sum_b[i] = sum_b[i - 1] + b[i]; } memset(dp, -1,sizeof(dp)); printf("%d\n", dfs(1, n, 1, n)); } return 0; }
#include<iostream> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define FOR(i,x,y) for(int i=(x);i<=(y);i++) #define DOR(i,x,y) for(int i=(x);i>=(y);i--) using namespace std; int dp[23][23][23][23]; int a[23],b[23]; int sa[23],sb[23]; int suma(int x,int y) { return sa[y]-sa[x-1]; } int sumb(int x,int y) { return sb[y]-sb[x-1]; } int main() { int T; scanf("%d",&T); while(T--) { memset(dp,0,sizeof(dp)); int n; scanf("%d",&n); FOR(i,1,n) { scanf("%d",&a[i]); sa[i]=sa[i-1]+a[i]; } FOR(i,1,n) { scanf("%d",&b[i]); sb[i]=sb[i-1]+b[i]; } FOR(l1,0,n)FOR(l2,0,n) //枚举两堆卡片被取的张数 FOR(L1,1,n-l1+1)FOR(L2,1,n-l2+1) //枚举两堆卡片左端点 { int R1=L1+l1-1,R2=L2+l2-1; if(l1)dp[L1][R1][L2][R2]=max(dp[L1][R1][L2][R2],suma(L1,R1)+sumb(L2,R2)-min(dp[L1+1][R1][L2][R2],dp[L1][R1-1][L2][R2])); if(l2)dp[L1][R1][L2][R2]=max(dp[L1][R1][L2][R2],suma(L1,R1)+sumb(L2,R2)-min(dp[L1][R1][L2+1][R2],dp[L1][R1][L2][R2-1])); } printf("%d\n",dp[1][n][1][n]); } return 0; }
You Are the One
HDU - 4283
题目:有一个队列,每个人有一个愤怒值D,如果他是第K个上场,不开心指数就为(K-1)*D。但是边上有一个小黑屋(其实就是个堆栈),可以一定程度上调整上场程序
http://acm.hdu.edu.cn/showproblem.php?pid=4283
题目表示就看了好久,不能理解~~~Q_Q
注意是一定程度上调整,也就是入堆栈的顺序是确定的,第一反应的贪心肯定是错的
由于受堆栈的影响,总觉得要维护堆栈的状态,这样就挂了~~~~
其实是一个区间DP,dp[i][j]表示从第i个人到第j个人这段区间的最小花费(是只考虑这j-i+1个人,不需要考虑前面有多少人)
那么对于dp[i][j]的第i个人,就有可能第1个上场,也可以第j-i+1个上场。考虑第K个上场
即在i+1之后的K-1个人是率先上场的,那么就出现了一个子问题 dp[i+1][i+1+k-1-1]表示在第i个人之前上场的
对于第i个人,由于是第k个上场的,那么愤怒值便是val[i]*(k-1)
其余的人是排在第k+1个之后出场的,也就是一个子问题dp[i+k][j],对于这个区间的人,由于排在第k+1个之后,所以整体愤怒值要加上k*(sigma(i+k--j))
#include<cstdio> #include<cstring> #include<algorithm> #include<vector> using namespace std; const int inf = 0x3f3f3f3f; const int N = 105; int dp[N][N]; int a[N], sum[N]; int main() { int t; int n; scanf("%d", &t); int ca = 0; while(t--) { scanf("%d", &n); for(int i = 1; i <= n; i++) { scanf("%d", &a[i]); sum[i] = sum[i - 1] + a[i]; } for(int i = 1; i <= n; i++) { for(int j = i + 1; j <= n; j++) { dp[i][j] = inf; } } for(int len = 2; len <= n; len++) { for(int i = 1; i <= n; i++) { int j = i + len - 1; if(j > n) break; for(int k = i; k <= j; k++) { dp[i][j] = min(dp[i][j], dp[i + 1][k] + a[i] * (k - i) + (sum[j] - sum[k]) * (k + 1 - i) + dp[k + 1][j]); } } } printf("Case #%d: %d\n", ++ca, dp[1][n]); } return 0; }