Loading

区间DP总结与DP技巧汇总

1简介

区间DP是一类模板DP,事实上,模板DP的状态设计非常有限,基本上有一定的套路。所以模板DP的难处在于判断出这是某模板DP,以及在模板的基础上进行扩展。
基本上,状态\(f_{lr}\)是区间DP基本状态。下面对区间DP题目进行总结。

2题目

2.1石子合并

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ull unsigned long long
#define N 101
#define M number
using namespace std;

int n,a[N*2];
ll f[2][N*2][N*2];
ll sum[N*2];//0->max  1->min

const ll INF=0x3f3f3f3f;

inline ll Max(ll a,ll b){
	return a>b?a:b;
}

inline ll Min(ll a,ll b){
	return a>b?b:a;
}

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];
	
	memset(f[0],0,sizeof(f[0]));
	memset(f[1],INF,sizeof(f[1]));
	for(int i=1;i<=2*n;i++) f[0][i][i]=f[1][i][i]=0;
	for(int j=2;j<=n;j++){
		for(int i=1;i<=2*n-j+1;i++){
			int r=i+j-1;
			for(int k=i;k<=r-1;k++){
//				printf("%lld %d %d %d\n",f[0][2][2],i,r,k);
				f[0][i][r]=Max(f[0][i][r],f[0][i][k]+f[0][k+1][r]+sum[r]-sum[i-1]);
//				printf("%lld %d %d %d\n",f[0][2][2],i,r,k);
				f[1][i][r]=Min(f[1][i][r],f[1][i][k]+f[1][k+1][r]+sum[r]-sum[i-1]);
			}
//			printf("%d %d %lld\n__________\n",i,r,f[0][i][r]);
		}
	}
	
	ll maxx=-1,minn=INF;
	for(int i=1;i<=n;i++){
		maxx=Max(maxx,f[0][i][i+n-1]);
		minn=Min(minn,f[1][i][i+n-1]);
	}
	printf("%lld\n%lld",minn,maxx);
	return 0;
}

这是最规矩的区间DP,主要思路在于对于一个区间,通过枚举断点进行转移,区间DP的特性是其DP顺序,即从短的区间开始更新,因此,第一个循环枚举的是区间长度,第二个是左端点,第三个是右端点。
以及,在处理环状时,通常是在后面在接上若干串,对每个长度为n的区间进行DP,取最值

2.2括号匹配

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ull unsigned long long
#define N 51
#define M number
using namespace std;

int f[N][N];
char s[N];
const int INF=0x3f3f3f3f;

inline int Min(int a,int b){
	return a>b?b:a;
}

int main(){
	scanf("%s",s+1);
	int len=strlen(s+1);
	memset(f,INF,sizeof(f));
	for(int i=1;i<=len;i++) f[i][i]=1;
	
	for(int i=2;i<=len;i++){
		for(int j=1;j<=len-i+1;j++){//l
			int l=j,r=j+i-1;
			if(s[l]==s[r]) f[l][r]=Min(f[l][r-1],f[l+1][r]);
			else{//f[l][r]=Min(f[l][r-1]+1,f[l+1][r]+1);
				for(int k=l;k<=r;k++) f[l][r]=Min(f[l][r],f[l][k]+f[k+1][r]);
			}
		}
	}
	printf("%d\n",f[1][len]);
}

这个题是在转移上有创新,其创新在于,如果新加的节点和右边能够匹配,那么方案数加1,否则,枚举断点,两种转移方式。

2.3 P1043 [NOIP2003 普及组] 数字游戏

先吐槽这竟然是普及组的题?
这道题在初始化上非常麻烦,练了练我的初始化水平。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ull unsigned long long
#define N 101
#define M 10
using namespace std;

const int INF=0x3f3f;

int n,m,a[N],sum[N];
int f[2][N][N][M];

inline int Max(int a,int b){
	return a>b?a:b;
}

inline int Min(int a,int b){
	return a>b?b:a;
}

int main(){
	scanf("%d %d",&n,&m);
	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];
	
	memset(f[1],INF,sizeof(f[1]));
//	for(int i=1;i<=2*n;i++) f[0][i][i][1]=f[1][i][i][1]=(a[i]%10+10)%10;
	
	for(int i=1;i<=2*n;i++){
		for(int j=i;j<=2*n;j++){
			f[1][i][j][1]=0;
			for(int k=i;k<=j;k++){
				f[0][i][j][1]+=a[k];
				f[1][i][j][1]+=a[k];
			}
			f[0][i][j][1]=(f[0][i][j][1]%10+10)%10;
			f[1][i][j][1]=(f[1][i][j][1]%10+10)%10;
		}
	}
//	printf("spec%d++++++\n",f[1][2][2][1]);
	
	for(int j=2;j<=n;j++){
		for(int i=1;i<=2*n-j+1;i++){
			int l=i,r=i+j-1;
			for(int k=2;k<=m;k++){
				for(int q=l+k-2;q<=r-1;q++){
					f[0][l][r][k]=Max(f[0][l][r][k],f[0][l][q][k-1]*(((sum[r]-sum[q])%10+10)%10));
					f[1][l][r][k]=Min(f[1][l][r][k],f[1][l][q][k-1]*(((sum[r]-sum[q])%10+10)%10));
//					printf("change:%d %d %d %d\n",l,r,k,f[1][l][r][k]);
//					printf("who?:%d %d %d %d %d\n",l,q,k-1,f[1][l][q][k-1],(((sum[r]-sum[q])%10+10)%10));
				}
			}
//			printf("end:%d %d %d %d\n--------------------\n",l,r,m,f[1][l][r][m]);
		}
	}
	int maxx=-1,minn=INF;
	for(int i=1;i<=n;i++){
		maxx=Max(maxx,f[0][i][i+n-1][m]);
		minn=Min(minn,f[1][i][i+n-1][m]);
	}
	printf("%d\n%d\n",minn,maxx);
}

总结

DP的一般套路是:
1.设计状态,要注意一定要不重不漏,所有能影响到答案的数据都要包含到状态里面。
2.初始化,基本上是第一项
3.转移,要注意无后效性,面面俱到。
4.可以关注数据范围,有时候范围会给我们以提醒。
基本技巧:
1.状态设计:一个条件,一个维度
2.增加条件,增加维度,改变状态(无后效性)。
3.求方案数的一般思路是在开一个与之对应的数组。
4.改变顺序,
5.消除冗余状态,简化空间(乌龟棋)
6.循环顺序的界定由转移的顺序决定。
7.把转移方程写下来之后再去写代码。
8.用所学过数学模型进行分析。

posted @ 2021-02-01 16:56  hyl天梦  阅读(184)  评论(0编辑  收藏  举报