D - 石子合并问题--直线版
HRBUST - 1818
这个题目是一个区间dp的入门,写完这个题目对于区间dp有那么一点点的感觉,不过还是不太会。
注意这个区间dp的定义
dp[i][j] 表示的应该是将连续的从 i 到第 j 堆的石块进行合并的最大值(或者最小值)
知道这个定义就很好求了,所以我们每次都要先枚举这个区间的长度,然后就是枚举这个区间的起点,然后就是枚举分段点。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <cstdio> #include <cstring> #include <cstdlib> #include <queue> #include <cmath> #include <stack> #include <vector> #include <algorithm> #define pi acos(-1) #define inf 0x3f3f3f3f using namespace std; typedef long long ll; const int maxn = 110; int dpmax[maxn][maxn], dpmin[maxn][maxn]; int stone[maxn]; int sum[maxn]; int main() { int n; while (scanf("%d", &n)!=EOF) { memset(dpmin, inf, sizeof(dpmin)); memset(dpmax, -inf, sizeof(dpmax)); memset(sum, 0, sizeof(sum)); for (int i = 1; i <= n; i++) { scanf("%d", &stone[i]); sum[i] = sum[i - 1] + stone[i]; dpmax[i][i] = 0; dpmin[i][i] = 0; } for (int i = 2; i <= n; i++) { for (int j = 1; j + i <= n + 1; j++) { int ends = j + i - 1; for (int k = j; k < ends; k++) { dpmin[j][ends] = min(dpmin[j][ends], dpmin[j][k] + dpmin[k + 1][ends] + sum[ends] - sum[j - 1]); dpmax[j][ends] = max(dpmax[j][ends], dpmax[j][k] + dpmax[k + 1][ends] + sum[ends] - sum[j - 1]); } } } printf("%d %d ", dpmin[1][n], dpmax[1][n]); } return 0; }
这个要是把题目看清楚了+看了一点点的题解就很简单了。
这个题目需要对题目进行一部分处理,推荐学长博客 https://blog.csdn.net/qq_39599067/article/details/80335949
学长博客讲的很清楚了。
我说一下我的理解吧,f[i][j]表示从i到j的异或和,dp[i][j]表示从i到j的最大的异或和
因为f[i][j]=f[i][j+1]^f[i+1][j]
所以这个dp就很好转移了 dp[i][j]=max(f[i][j],max(dp[i][j-1],dp[i+1][j])
这个转移方程我觉得还比较难理解,对于dp[i][j] 应该是由三个状态转移过来的,一个是本身的异或和,一个不包括左端点的最大值,一个是不包括右端点的最大值,
这个可以去看学长的那个图。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <cstdio> #include <iostream> #include <cstdlib> #include <cstring> #include <queue> #include <vector> #include <algorithm> #define inf 0x3f3f3f3f using namespace std; const int maxn = 5e3 + 100; int dp[maxn][maxn], f[maxn][maxn]; int main() { int n; scanf("%d", &n); for(int i=1;i<=n;i++) { scanf("%d", &dp[i][i]); f[i][i] = dp[i][i]; } for(int i=1;i<=n;i++) { for(int j=1;j+i<=n;j++) { f[j][j + i] = f[j][j + i - 1] ^ f[j + 1][j + i]; } } for(int i=1;i<=n;i++) { for(int j=1;j+i<=n;j++) { dp[j][j + i] = max(f[j][i + j], max(dp[j][j + i - 1], dp[j + 1][j + i])); } } int q; scanf("%d", &q); while(q--) { int l, r; scanf("%d%d", &l, &r); printf("%d ", dp[l][r]); } return 0; }
B - Halloween Costumes
这个题目我觉得还是一个比较难的区间dp,相对于这个之前可以说算是裸题了。
这个我看了题解之后,按照题解的思路去敲代码,推荐博客:https://www.cnblogs.com/DOLFAMINGO/p/7927432.html
他是把这个题目进行了一定的转化,转化成了涂色,就是把给定一个区间,每次可以为一段连续的子区间刷一种颜色。问最少需要刷多少次,能得到目标的区间。
该题解对左端点进行讨论,这个算是一个切入点吧,因为对于左端点和右端点进行讨论应该都是可以的,但是对于左端点会更加简单一些。
怎么对左端点进行讨论呢
我的理解:
预处理就是假设每一个都是要涂成不一样的颜色
这个应该是理解为先涂成目标这样子,然后去判断涂了多少层。
对左端点进行考虑。
先假设左端点涂的是一种新颜色。
所以dp[i][j]=dp[i+1][j]+1;
然后去判断这个区间有没有和它一样的颜色,如果有的话,他们应该是同一个时刻涂的,
所以就暂时忽略它,以这个点为分段点对该点左右颜色总和加起来 去最小。
其实看了题解理解我觉得还是挺难的,能想到的人,我感觉挺变态的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <cstdio> #include <cstdlib> #include <queue> #include <vector> #include <algorithm> #include <cstring> #include <iostream> #define inf 0x3f3f3f3f using namespace std; const int maxn = 1e2 + 10; int dp[maxn][maxn]; int a[maxn]; int main() { int t; scanf("%d", &t); for(int cas=1;cas<=t;cas++) { int n; scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); memset(dp, 0, sizeof(dp)); for (int i = 1; i <= n; i++) dp[i][i] = 1; for(int i=2;i<=n;i++) { for(int j=1;j+i-1<=n;j++) { int ends = i + j - 1; dp[j][ends] = dp[j + 1][ends] + 1; for(int k=j+1;k<=ends;k++) { if(a[j]==a[k]) { dp[j][ends] = min(dp[j][ends], dp[j][k - 1] + dp[k + 1][ends]); } } } } printf("Case %d: %d ",cas, dp[1][n]); } return 0; }
H - String painter
来写这个题目,如果你解决了上面的这个题目,那么这个就变得很简单了,
从上面得题目我们知道了怎么去把一个空白字符串涂成我们想要的字符串。
现在我们是把一个已知的字符串涂成我们想要的,这个怎么写呢?
这个应该是去枚举每一位的字符,如果这个字符和所求的字符相同就不需要再涂了,
但是如果不一样就需要给它重新涂色,这个涂色可不是简单的加上一个颜色,
而是要从整体的角度去看如何给她涂色最优,所以我们要枚举这个和这个之前的每一个位置来判断最优。
区间dp