区间dp小记

一切都要从几堆石头说起
经典例题合并石子
我们可以设\(dp[1][i][j]\)表示从第\(i\)堆合并到第\(j\)堆的最大得分,\(dp[0][i][j]\)表示最小得分
那么我们可以通过枚举断点来转移方程
\(dp[k][i][j]=max(min){dp[k][i][l]+dp[k][l+1][j]}\)
由于是个环,我们可以把1到\(n\)复制,从而把环拆成链
所以区间\(dp\)就是确定要合并的区间的状态然后枚举断点+考虑特殊情况即可
我们来几道题康康
P4170涂色
这其实就是个加强版的石子合并,但不知道为什么它蓝了
我们依旧设\(dp[i][j]\)表示把\([i,j]\)涂成目标样子所需要的最小次数
\(s[i]==s[j]\),则\(dp[i][j]=min\){\(dp[i][k]+dp[k+1][j]-1\)},因为可以将两端相同的颜色视为涂一次造成的。
若不同,则\(dp[i][j]=min\){\(dp[i][k]+dp[k+1][j]\)}

 #include<iostream>
 #include<cstdio>
 #include<cstring>
 #include<cstdlib>
 #include<algorithm>
 #include<cmath>
 #include<queue>
 #include<vector>
 using namespace std;
 int read()
 {
 	char ch=getchar();
 	int x=0;bool f=0;
 	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return f?-x:x;
 } 
 int dp[59][59];
 char na[59];
 int main()
 {
 	scanf("%s",na+1);
	int len=strlen(na+1);
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<=len;i++)
	dp[i][i]=1;
	for(int l=1;l<=len;l++)
	{
		for(int i=1;i+l-1<=len;i++)
		{
			int j=i+l-1;
			for(int k=i;k<j;k++)
			{
				if(na[i]==na[j]) dp[i][j]=min(dp[i][j],dp[i] 
[k]+dp[k+1][j]-1);
				else dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
			}
		}
	}
	printf("%d",dp[1][len]);
 }

P4342Polygen
这是一道远古IOI题目
我们发现这很像石子合并,所以考虑用区间\(dp\)
首先考虑断边问题
这是个环,我们可以像石子合并那样,把链复制为原来的两倍来达到环的效果
如果我们是从\(i\)合并到\(j\),则相当于没有使用边\(i\),即断开边\(i\)
再来看\(dp\)式子
如果枚举的断点处的边是"+",则\(dp[i][j]=max\){\(dp[i][k]+dp[k+1][j]\)}
如果是\(\times\),那么可能会出现两个负数相乘然后得到更大的值的情况,所以我们还要考虑维护一个\(dp_{min}[i][j]\)
这样最大值可以由最大×最大,最小×最小,最大×最小(一正一负)更新而来
此时\(dp[i][j]=max\)\((dp[i][k]*dp[k+1][j],dp_{min}[i][k]*dp_{min}[k+1][j],dp[i][k]*dp_{min}[k+1][j],dp_{min}[i][k]*dp[k+1][j])\)
\(dp_{min}\)的维护:
加法:两个最小值相加即可
乘法:考虑最小×最小,最大×最小
\(Code\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
int read()
{
	char ch=getchar();
	int x=0;bool f=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return f?-x:x;
} 
int n,nd[109],ed[109];
ll dp[2][109][109],ans;
int main()
{
	n=read();
	for(int i=1;i<=2*n;i++)
	{
		if(i%2)
		{
			char ch=getchar();
			while(ch!='t'&&ch!='x') ch=getchar();
			if(ch=='t') ed[i/2+1]=1;
			if(ch=='x') ed[i/2+1]=2;
		}
		else
			nd[i/2]=read();	
	}
	memset(dp[1],-0x3f,sizeof(dp[1]));
	memset(dp[0],0x3f,sizeof(dp[0]));
	ans=-2147483647;
	for(int i=1;i<=2*n;i++)
	{
		if(i>n) nd[i]=nd[i-n],ed[i]=ed[i-n];
		dp[0][i][i]=dp[1][i][i]=nd[i]; 
	} 
	for(int len=2;len<=n;len++)
	{
		for(int i=1;i+len-1<=2*n;i++)
		{
			int j=i+len-1;
			for(int l=i;l<j;l++)
			{
				if(ed[l+1]==1)
				{
					dp[1][i][j]=max(dp[1][i][j],dp[1][i][l]+dp[1][l+1][j]);
					dp[0][i][j]=min(dp[0][i][j],dp[0][i][l]+dp[0][l+1][j]);
				}
				else
				{
					dp[1][i][j]=max(dp[1][i][j],dp[1][i][l]*dp[1][l+1][j]);
					dp[1][i][j]=max(dp[1][i][j],dp[0][i][l]*dp[0][l+1][j]);
					dp[1][i][j]=max(dp[1][i][j],dp[1][i][l]*dp[0][l+1][j]);
					dp[1][i][j]=max(dp[1][i][j],dp[0][i][l]*dp[1][l+1][j]);
					dp[0][i][j]=min(dp[0][i][j],dp[0][i][l]*dp[0][l+1][j]); 
				        dp[0][i][j]=min(dp[0][i][j],dp[1][i][l]*dp[0][l+1][j]);
				        dp[0][i][j]=min(dp[0][i][j],dp[0][i][l]*dp[1][l+1][j]);
				        dp[0][i][j]=min(dp[0][i][j],dp[1][i][l]*dp[1][l+1][j]);
				}
			}
		}
	} 
	for(int i=1;i+n-1<=2*n;i++)
	 ans=max(ans,dp[1][i][i+n-1]);
	printf("%lld\n",ans);
	for(int i=1;i<=n;i++)
	 if(dp[1][i][i+n-1]==ans) printf("%d ",i);
}

P5851
奶牛吃掉区间\([i,j]\)的派,可以视作合并区间\([i,j]\),得分即为该奶牛的体重
\(dp[i][j]\)表示合并区间\([i,j]\)的最大得分
我们可以枚举被新的奶牛吃掉的派\(k\)(\(k\in [i,j]\)),设\(qry[i][j][k]\)表示符合\(i\leq l[i] \leq k \leq r[i] \leq j\)的最大的\(w[i]\)
所以\(dp[i][j]=max\){\(qry[i][j][k]+dp[i][k-1]+dp[k+1][j]\)}
注意如果\(k-1<i\)\(k+1>j\)时对应的那部分就不需要了
怎么求\(qry[i][j][k]\)?
在读入时,我们可以得到\(qry[l[i]][r[i]][k]=w[i]\)
我们可以扩散得到\(qry[i][j][k]\)
\(qry[i-1][j][k]=max(qry[i][j][k],qry[i-1][j][k])\)
\(qry[i][j+1][k]=max(qry[i][j][k],qry[i][j+1][k])\)
\(Code\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
ll read()
{
	char ch=getchar();
	ll x=0;bool f=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return f?-x:x;
} 
ll n,m;
ll dp[309][309],qry[309][309][309];
int main()
{
	n=read();m=read();
	for(int i=1;i<=m;i++)
	{
		ll ww=read(),l=read(),r=read();
		for(int j=l;j<=r;j++)
		 qry[l][r][j]=ww;
	}
	for(int k=1;k<=n;k++)
	{
		for(int i=k;i>=1;i--)
		{
			for(int j=k;j<=n;j++)
			{
				if(i!=1)
			     qry[i-1][j][k]=max(qry[i-1][j][k],qry[i][j][k]);
				if(j!=n)
				 qry[i][j+1][k]=max(qry[i][j+1][k],qry[i][j][k]);	
			} 
		}
	}
	for(int len=1;len<=n;len++)
	{
		for(int i=1;i+len-1<=n;i++)
		{
			int j=i+len-1;
			for(int k=i;k<j;k++)
			 dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
			for(int k=i;k<=j;k++)
			{
				if(k>i&&k<j)
			     dp[i][j]=max(dp[i][j],qry[i][j][k]+dp[i][k-1]+dp[k+1][j]);			
			    if(k==i) dp[i][j]=max(dp[i][j],dp[k+1][j]+qry[i][j][k]);
			    else dp[i][j]=max(dp[i][j],dp[i][k-1]+qry[i][j][k]);
		    }
		}
	}
	printf("%lld\n",dp[1][n]);
}
posted @ 2020-05-29 17:32  千载煜  阅读(151)  评论(0编辑  收藏  举报