区间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]);
}