区间DP
Posted ShadowAA
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了区间DP相关的知识,希望对你有一定的参考价值。
区间DP
区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来有很大的关系。
例题
石子合并 洛谷1880
#include<bits/stdc++.h>
using namespace std;
int n,i,j,k,l,ma,mi,a[205],b[205],f[205][205],g[205][205];
int main()
cin>>n;
for (i=1;i<=n;i++)
cin>>a[i];
b[i]=b[i-1]+a[i];
for (i=n+1;i<=2*n;i++)
b[i]=b[i-1]+a[i-n];
mi=INT_MAX;
for (l=1;l<=n;l++)
memset(g,64,sizeof(g));
for (i=1;i<=n*2;i++)
g[i][i]=0;
memset(f,0,sizeof(f));
for (i=2;i<=n;i++)
for (j=l;j<=n-i+l;j++)
for (k=j;k<=j+i-2;k++)
f[j][i+j-1]=max(f[j][i+j-1],f[j][k]+f[k+1][i+j-1]+b[i+j-1]-b[j-1]);
g[j][i+j-1]=min(g[j][i+j-1],g[j][k]+g[k+1][i+j-1]+b[i+j-1]-b[j-1]);
ma=max(ma,f[l][n+l-1]);
mi=min(mi,g[l][n+l-1]);
cout<<mi<<\'\\n\'<<ma<<\'\\n\';
关路灯 洛谷1220
#include<bits/stdc++.h>
using namespace std;
int n,m,i,j,a[100],b[100],c[100],f[100][100][2];
int main()
cin>>n>>m;
for (i=1;i<=n;i++)
cin>>a[i]>>b[i];
c[i]=c[i-1]+b[i];
memset(f,127,sizeof(f));
f[m][m][0]=0;f[m][m][1]=0;
for (i=2;i<=n;i++)
for (j=1;j<=n-i+1;j++)
f[j][j+i-1][0]=min(f[j+1][j+i-1][1]+(a[j+i-1]-a[j])*(c[n]-c[j+i-1]+c[j]),
f[j+1][j+i-1][0]+(a[j+1]-a[j])*(c[j]+c[n]-c[j+i-1]));
f[j][j+i-1][1]=min(f[j][j+i-2][0]+(a[j+i-1]-a[j])*(c[n]-c[j+i-2]+c[j-1]),
f[j][j+i-2][1]+(a[j+i-1]-a[j+i-2])*(c[n]-c[j+i-2]+c[j-1]));
cout<<min(f[1][n][0],f[1][n][1]);
洛谷 3147
Bessie喜欢在手机上下游戏玩(……),然而她蹄子太大,很难在小小的手机屏幕上面操作。
她被她最近玩的一款游戏迷住了,游戏一开始有n个正整数,(2<=n<=262144),范围在1-40。在一步中,贝西可以选相邻的两个相同的数,然后合并成一个比原来的大一的数(例如两个7合并成一个8),目标是使得最大的数最大,请帮助Bessie来求最大值。
$$
f[i][j] 保存 i这个数 以 j作为开头的合并区间的结尾,若没有就是0
$$
#include<bits/stdc++.h>
using namespace std;
int n,i,x,j,s,f[60][300005];
int main()
cin>>n;
for (i=1;i<=n;i++)
cin>>x;
f[x][i]=i+1;
for (i=2;i<=58;i++)
for (j=1;j<=n;j++)
if (!f[i][j])
f[i][j]=f[i-1][f[i-1][j]];
if (f[i][j])
s=i;
cout<<s<<\'\\n\';
合唱队 洛谷3205
#include<bits/stdc++.h>
using namespace std;
int n,i,j,a[1005],f[1005][1005][2];
const int mod=19650827;
int main()
cin>>n;
for (i=1;i<=n;i++)
cin>>a[i];
f[i][i][0]=1;
for (i=2;i<=n;i++)
for (j=1;j<=n-i+1;j++)
if (a[j]<a[j+1])
f[j][i+j-1][0]=(f[j][i+j-1][0]+f[j+1][i+j-1][0])%mod;
if (a[j]<a[i+j-1])
f[j][i+j-1][0]=(f[j][i+j-1][0]+f[j+1][i+j-1][1])%mod;
if (a[i+j-1]>a[i+j-2])
f[j][i+j-1][1]=(f[j][i+j-1][1]+f[j][i+j-2][1])%mod;
if (a[i+j-1]>a[j])
f[j][i+j-1][1]=(f[j][i+j-1][1]+f[j][i+j-2][0])%mod;
cout<<(f[1][n][0]+f[1][n][1])%mod<<\'\\n\';
涂色 洛谷4170
假设你有一条长度为 $5$ 的木板,初始时没有涂过任何颜色。你希望把它的 $5$ 个单位长度分别涂上红、绿、蓝、绿、红色,用一个长度为 $5$ 的字符串表示这个目标:$\\textttRGBGR$。
每次你可以把一段连续的木板涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色。例如第一次把木板涂成 $\\textttRRRRR$,第二次涂成 $\\textttRGGGR$,第三次涂成 $\\textttRGBGR$,达到目标。
用尽量少的涂色次数达到目标。
#include<bits/stdc++.h>
using namespace std;
int n,i,j,k,f[101][101];
string s;
int main()
cin>>s;
n=s.length();
s=\'.\'+s;
memset(f,127,sizeof(f));
for (i=1;i<=n;i++)
f[i][i]=1;
for (i=2;i<=n;i++)
for (j=1;j<=n-i+1;j++)
if (s[j]==s[i+j-1])
f[j][i+j-1]=min(f[j][i+j-2],f[j+1][i+j-1]);
for (k=j;k<=i+j-2;k++)
f[j][i+j-1]=min(f[j][i+j-1],f[j][k]+f[k+1][i+j-1]);
cout<<f[1][n];
玩具取名 洛谷4290
#include<bits/stdc++.h>
using namespace std;
int W,I,N,G,i,n,j,k,l,o;
string ww[20],ii[20],nn[20],gg[20],s;
bool f[205][205][4];
int judge(char ch)
if (ch==\'W\') return 0;
if (ch==\'I\') return 1;
if (ch==\'N\') return 2;
if (ch==\'G\') return 3;
return -1;
int main()
cin>>W>>I>>N>>G;
for (i=1;i<=W;i++)
cin>>ww[i];
for (i=1;i<=I;i++)
cin>>ii[i];
for (i=1;i<=N;i++)
cin>>nn[i];
for (i=1;i<=G;i++)
cin>>gg[i];
cin>>s;
n=s.length();
s=\'.\'+s;
for (i=1;i<=n;i++)
f[i][i][judge(s[i])]=1;
for (i=2;i<=n;i++)
for (j=1,k=i;k<=n;j++,k++)
for (l=j;l<=k-1;l++)
for (o=1;o<=W;o++)
f[j][k][0]=f[j][k][0] or f[j][l][judge(ww[o][0])] and f[l+1][k][judge(ww[o][1])];
for (o=1;o<=I;o++)
f[j][k][1]=f[j][k][1] or f[j][l][judge(ii[o][0])] and f[l+1][k][judge(ii[o][1])];
for (o=1;o<=N;o++)
f[j][k][2]=f[j][k][2] or f[j][l][judge(nn[o][0])] and f[l+1][k][judge(nn[o][1])];
for (o=1;o<=G;o++)
f[j][k][3]=f[j][k][3] or f[j][l][judge(gg[o][0])] and f[l+1][k][judge(gg[o][1])];
if (f[1][n][0])cout<<\'W\';
if (f[1][n][1])cout<<\'I\';
if (f[1][n][2])cout<<\'N\';
if (f[1][n][3])cout<<\'G\';
if ((!f[1][n][0])and(!f[1][n][1])and(!f[1][n][2])and(!f[1][n][3]))
cout<<"The name is wrong!";
cout<<\'\\n\';
字符串折叠 洛谷4302
#include<bits/stdc++.h>
using namespace std;
string s;int n,i,l,k,o,r,len,t,w,f[105][105];
unsigned long long a[105],b[105];
const int base=23333233;
int main()
cin>>s;
n=s.length();
s=\'.\'+s;
for (i=1;i<=n;i++)
a[i]=a[i-1]*base+s[i];
b[0]=1;
for (i=1;i<=n;i++)
b[i]=b[i-1]*base;
memset(f,127,sizeof(f));
for (i=1;i<=n;i++)
f[i][i]=1;
for (i=2;i<=n;i++)
for (l=1,r=i;r<=n;l++,r++)
len=r-l+1;
for (k=1;k<=len/2;k++)
if (len%k==0)
for (o=2;o<=len/k;o++)
if (a[l+o*k-1]-a[(o-1)*k+l-1]*b[k]!=a[l+(o-1)*k-1]-a[(o-2)*k+l-1]*b[k])
break;
if (o!=len/k+1)
continue;
t=len/k;w=0;
while (t>0)
t=t/10;
w++;
f[l][r]=min(f[l][r],f[l][l+k-1]+2+w);
for (k=l;k<r;k++)
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]);
cout<<f[1][n]<<\'\\n\';
Greedy Pie Eaters P 洛谷5851
#include<bits/stdc++.h>
using namespace std;
int n,m,i,j,l,r,k,a,b,c,f[305][305],g[305][305][305];
int main()
cin>>n>>m;
for (i=1;i<=m;i++)
cin>>a>>b>>c;
for (j=b;j<=c;j++)
g[j][b][c]=a;
for (i=1;i<=n;i++)
for (j=i;j>=1;j--)
for (k=i;k<=n;k++)
if (j!=1)g[i][j-1][k]=max(g[i][j-1][k],g[i][j][k]);
if (k!=n)g[i][j][k+1]=max(g[i][j][k+1],g[i][j][k]);
for (i=1;i<=n;i++)
for (l=1,r=i;r<=n;l++,r++)
for (k=l;k<r;k++)
f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]);
for (k=l;k<=r;k++)
f[l][r]=max(f[l][r],f[l][k-1]+f[k+1][r]+g[k][l][r]);
cout<<f[1][n]<<\'\\n\';
合并饭团 洛谷4805
#include<bits/stdc++.h>
using namespace std;
int n,i,l,r,k,j,x,ma,t,w,m,f[505][505],a[505];
int main()
cin>>n;
for (i=1;i<=n;i++)
for (j=i+1;j<=n;j++)
f[i][j]=-1;
for (i=1;i<=n;i++)
cin>>f[i][i];
a[i]=a[i-1]+f[i][i];
for (i=2;i<=n;i++)
for (l=1,r=i;r<=n;l++,r++)
for (t=l,w=r-1;t<=w;t++)
while (a[r]-a[w]<a[t]-a[l-1])w--;
if (w<t) break;
if (a[r]-a[w]!=a[t]-a[l-1])
continue;
if ((f[l][t]!=-1)and(f[t+1][w]!=-1)and(f[w+1][r]!=-1)and(f[l][t]==f[w+1][r]))
f[l][r]=max(f[l][r],f[l][t]+f[t+1][w]+f[w+1][r]);
for (i=1;i<=n;i++)
for (j=i;j<=n;j++)
ma=max(ma,f[i][j]);
cout<<ma<<\'\\n\';
嘻嘻
区间dp
区间dp
区间dp的一般形式
for(int len = 1; len < n; len++) //枚举区间长度
{
for(int i = 1; i+len <= n; i++) //枚举区间左端点
{
int j = i+len; //区间右端点
for(int k = i; k < j; k++) //枚举k
{
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+g[i][k]+g[k+1][j]);
}
}
}
以上是关于区间DP的主要内容,如果未能解决你的问题,请参考以下文章