区间DP学习笔记

基本概念:区间DP是什么?正如其名,是以区间长度为阶段的一种DP。这类DP的解法较为固定,先枚举区间长度,再枚举左端点,之后枚举断点,分为左右两部分进行求解,总的来说,可以转移出这样一个模板状态转移方程dp[i][j] = min(dp[i][k] + dp[i + 1][k] + 决策),决策根据题目要求所答。

光知道到基本概念还不行,讲完了简单的基本概念,来两道简单的例题过过手。

例题1:石子合并

题目传送门

分析:本题是区间DP最经典的一道模板题,几乎是区间DP的模板,但很容易误解成贪心,样例也制造了这种假象。当要i到j合并在一起时,我们可以想象成合并i到k,合并k + 1到j,再合并i到j,这样,我们就可以套出区间DP的模板了。除外区间DP,另外的一个关键的地方在于处理环形,我们可以把这个环的长度乘以2,再剖成一条链,这样就可以保证合并顺序的正确性了。

区间dp代码:

	for(int len = 2;len <= n;len ++) {
		for(int i = 1;i <= 2 * n - len + 1;i ++) {
			int j = i + len - 1;
			DP2[i][j] = 0x7fffffff / 2;
			DP1[i][j] = 0;
			for(int k = i;k < j;k ++) {
				DP1[i][j] = max(DP1[i][j],DP1[i][k] + DP1[k + 1][j]);
				DP2[i][j] = min(DP2[i][j],DP2[i][k] + DP2[k + 1][j]);
			}
			DP1[i][j] += sum[j] - sum[i - 1];
			DP2[i][j] += sum[j] - sum[i - 1];
    }

环形处理代码:

a[i + n] = a[i];

源代码:

#include 
#include 
#include 
using namespace std;
const int MAXN = 205;
int DP1[MAXN][MAXN],DP2[MAXN][MAXN],n,a[MAXN],sum[MAXN] = {0}; 
int main() {
	scanf("%d",&n);
	for(int i = 1;i <= n;i ++) {
		scanf("%d",&a[i]);
		a[i + n] = a[i];
    }
    for(int i = 1;i <= 2 * n;i ++) {
    	sum[i] = sum[i - 1] + a[i];
    	DP2[i][i] = 0;
    	DP1[i][i] = 0;
	}
	for(int len = 2;len <= n;len ++) {
		for(int i = 1;i <= 2 * n - len + 1;i ++) {
			int j = i + len - 1;
			DP2[i][j] = 0x7fffffff / 2;
			DP1[i][j] = 0;
			for(int k = i;k < j;k ++) {
				DP1[i][j] = max(DP1[i][j],DP1[i][k] + DP1[k + 1][j]);
				DP2[i][j] = min(DP2[i][j],DP2[i][k] + DP2[k + 1][j]);
			}
			DP1[i][j] += sum[j] - sum[i - 1];
			DP2[i][j] += sum[j] - sum[i - 1];
		}
	}
	int ans1 = 0,ans2 = 0x7fffffff / 2;
	for(int i = 1;i <= n;i ++) {
		ans1 = max(ans1,DP1[i][i + n - 1]);
	}
	for(int i = 1;i <= n;i ++) {
		ans2 = min(ans2,DP2[i][i + n - 1]);
	}
	printf("%d\n%d",ans2,ans1);
	return 0;
}
例题2:[CQOI2007]涂色

题目传送门

虽然这是luogu上的蓝标,但总的来说,这道题还是比较简单的,比石子合并还少了一个环形的考虑。
分析:如果s[i] = s[j],在开始时多涂一个便可,于是有这样一个方程dp[i][j]=min(dp[i][j-1],dp[i+1][j]),否则,就变成了一个区间DP,把子串分成两部分来涂色,于是就有了dp[i][j] = min(dp[i][k] + dp[k + 1][j]),得到了转移方程,便可以得到主代码:

for(int len = 1;len < n;len ++) {
		for(int i = 1;i <= n;i ++) {
			int j = i + len;
			if(i > n)
			break;
			if(s[i] == s[j])
			dp[i][j] = min(dp[i][j - 1],dp[i + 1][j]);
			else
			for(int k = i;k < j;k ++)
			dp[i][j] = min(dp[i][j],dp[i][k] + dp[k + 1][j]);
		}
	}

初始状态为dp[i][i] = 0,其余的为极大值,目标:dp[1][n]
源代码:

#include 
#include 
#include 
using namespace std;
const int MAXN = 1005;
char s[MAXN];
int dp[MAXN][MAXN];
int main() {
	scanf("%s",s + 1);
	int n = strlen(s + 1);
	memset(dp,0x3f3f3f3f,sizeof(dp));
	for(int i = 1;i < MAXN;i ++)
	dp[i][i] = 1;
	for(int len = 1;len < n;len ++) {
		for(int i = 1;i <= n;i ++) {
			int j = i + len;
			if(i > n)
			break;
			if(s[i] == s[j])
			dp[i][j] = min(dp[i][j - 1],dp[i + 1][j]);
			else
			for(int k = i;k < j;k ++)
			dp[i][j] = min(dp[i][j],dp[i][k] + dp[k + 1][j]);
		}
	}
	printf("%d",dp[1][n]);
	return 0;
} 

END

你可能感兴趣的:(DP)