Part1:总概
区间dp
顾名思义,就是解决一些区间内最优值的问题,通常的时间复杂度为n2n^2n2 或者 n3n^3n3
大致思路:
-
首先确定状态
-
初始化长度为1(or 2,3…具体因题而异)的dp数组的值
-
然后枚举区间长度,枚举区间的起始点,(有的题目还需要枚举断点) 由小区间转移到大区间。
-
最后dp[1][n]dp[1][n]dp[1][n]往往就是答案。
Part2:例题精讲
NOI1995 石子合并
思路分析:
贪心~很明显是不行的
好的,那我们还是开始想dp吧。区间dp常用的一个状态就是dp[i][j]表示i~j这个区间的最优值是多少?
我们可以看出题目中这个合并过程,很像是区间dp的一种合并,也就是说dp[i][j]dp[i][j]dp[i][j]可以由dp[i][k]dp[i][k]dp[i][k]和dp[k+1][j]dp[k+1][j]dp[k+1][j]转移过
那么我们自然而然的想到要枚举上一次合并是在哪个位置,也就是断点k,然后进行转
不过,由于这个题目的特殊限制,我们需要记录g[i][j]表示i到j这个区间有多少石子,来进行辅助转移
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+g[i][k]+g[k+1][j])(i<=k<=j)f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+g[i][k]+g[k+1][j]) ( i<=k<=j)f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+g[i][k]+g[k+1][j])(i<=k<=j)
对于环,我们只需要把整个数组复制一遍,然后在统计答案的时候,把1-n开头的长度为n的区间的答案求一个min 就可以啦
Code
#include<bits/stdc++.h>
using namespace std;
int n,minl,maxl,f1[300][300],f2[300][300],num[300];
int s[300];
int d(int i,int j)
{
return s[j]-s[i-1];
}
int main()
{
cin>>n;
for(int i=1;i<=n+n;i++)
{
cin>>num[i];
num[i+n]=num[i];
s[i]=s[i-1]+num[i];
}
for(int p=1;p<n;p++)
{
for(int i=1,j=i+p;(j<n+n)&&(i<n+n);i++,j=i+p)
{
f2[i][j]=999999999;
for(int k=i;k<j;k++)
{
f1[i][j]=max(f1[i][j],f1[i][k]+f1[k+1][j]+d(i,j));
f2[i][j]=min(f2[i][j],f2[i][k]+f2[k+1][j]+d(i,j));
}
}
}
minl=999999999;
for(int i=1;i<=n;i++)
{
maxl=max(maxl,f1[i][i+n-1]);
minl=min(minl,f2[i][i+n-1]);
}
cout<<minl<<endl<<maxl;
return 0;
}
不过,这道题在hdu上(hdu3506)n^3的时间复杂度过不了
那么我们就需要考虑,四边形不等式优化!!
如果有f[a][c]+f[b][d]<=f[b][c]+f[a][d]f[a][c]+f[b][d]<=f[b][c]+f[a][d]f[a][c]+f[b][d]<=f[b][c]+f[a][d]
可以理解为一句话,交叉小于包含,即交叉的两个区间,a到c和b到d的值满足小于等于包含的两个区间[bc包含于ad])
则说这个东西满足四边形不等式,当然这个东西可能是dp数组,也可以是其他数组
这里有两个定理
1、如果上述的w函数同时满足区间包含单调性和四边形不等式性质,那么函数dp也满足四边形不等式性质
我们再定义s(i,j)s(i,j)s(i,j)表示 dp(i,j)dp(i,j)dp(i,j) 取得最优值时对应的下标(即 i≤k≤ji≤k≤ji≤k≤j 时,k 处的 dp 值最大,则 s(i,j)=ks(i,j)=ks(i,j)=k此时有如下定理
2、假如dp(i,j)dp(i,j)dp(i,j)满足四边形不等式,那么s(i,j)s(i,j)s(i,j)单调,即 s(i,j)≤s(i,j+1)≤s(i+1,j+1)s(i,j)≤s(i,j+1)≤s(i+1,j+1)s(i,j)≤s(i,j+1)≤s(i+1,j+1)
那么我们就可以开一个s数组 ,s[i][j]s[i][j]s[i][j]这个数组记录i ji~ji j这个区间的转移的最优点是哪个点
那么枚举断点是时候 直接可以从s[i][j−1] s[i+1[j]s[i][j-1] ~ s[i+1[j]s[i][j−1] s[i+1[j]枚举转移
(1)当函数w[i,j]满足w[i,j]+w[i′,j′]<=w[i′,j]+w[i,j′],i<=i′<=j<=j′w[i,j]+w[i',j']<=w[i',j]+w[i,j'],i<=i'<=j<=j'w[i,j]+w[i′,j′]<=w[i′,j]+w[i,j′],i<=i′<=j<=j′时,称w满足四边形不等式。
(2)当函数w[i,j]满足w[i’,j]≤w[i,j’],i<=i′<=j<=j′w[i’,j]≤w[i,j’],i<=i'<=j<=j'w[i’,j]≤w[i,j’],i<=i′<=j<=j′时称w关于区间包含关系单调。
很明显的,一开始定义的w[I,j]满足区间包含关系和四边形不等式。
现在我们要证明f[I,j]=minf[I,k]+f[k+1,j]+w[I,j]i<=k<jf[I,j]=min{f[I,k]+f[k+1,j]+w[I,j]}{i<=k<j}f[I,j]=minf[I,k]+f[k+1,j]+w[I,j]i<=k<j也满足四边形不等式
我们利用数学归纳法对四边形不等式中的区间长度进行归纳。
证明如下:
当i=i’或j=j’时,不等式显然成立。由此可知,当l≤1时,函数f满足四边形不等式。
下面分两种情形进行归纳证明:
-
情形1:i<i’=j<j’i<i’=j<j’i<i’=j<j’
在这种情形下,四边形不等式简化为如下的不等式:f[i,j]+f[j,j’]≤f[i,j’]+0f[i,j]+f[j,j’] ≤f[i,j’]+0f[i,j]+f[j,j’]≤f[i,j’]+0,设k=maxp∣f[i,j’]=f[i,p−1]+f[p,j’]+w[i,j’]k=max{p| f[i,j’]=f[i,p-1]+f[p,j’]+w[i,j’] }k=maxp∣f[i,j’]=f[i,p−1]+f[p,j’]+w[i,j’],再分两种情形k≤jk≤jk≤j或k>jk>jk>j。下面只讨论k≤jk≤jk≤j,k>jk>jk>j 的情况是类似的。 -
情形1.1:k≤jk≤jk≤j,此时:
-
情形1.2:k>jk>jk>j,略去
-
情形2:i<i’<j<j’i<i’<j<j’i<i’<j<j’
设 y=maxp∣f[i’,j]=f[i’,p−1]+f[p,j]+w[i’,j]y=max{p | f[i’,j]=f[i’,p-1]+f[p,j]+w[i’,j] }y=maxp∣f[i’,j]=f[i’,p−1]+f[p,j]+w[i’,j]f[p,j’]+w[i,j’]{f[p,j’]+w[i,j’] }f[p,j’]+w[i,j’]
仍需再分两种情形讨论,即z≤yz≤yz≤y或z>yz>yz>y。下面只讨论z≤yz≤yz≤y,z>yz>yz>y的情况是类似的。
情形2.1,i<z≤y≤ji<z≤y≤ji<z≤y≤j。
显然的,我们有:
f[i,j]+f[i′,j′]<=w[i,j]+f[i,z−1]+f[z,j]+w[i′,j′]+f[i′,y−1]+f[y,j′]f[i,j]+f[i',j']<=w[i,j]+f[i,z-1]+f[z,j]+w[i',j']+f[i',y-1]+f[y,j']f[i,j]+f[i′,j′]<=w[i,j]+f[i,z−1]+f[z,j]+w[i′,j′]+f[i′,y−1]+f[y,j′]
因为w满足四边形不等式,所以:
f[i,j]+f[i′,j′]<=w[i,j′]+w[i′,j]+f[i′,y−1]+f[i,z−1]+f[z,j]+f[y,j′]f[i,j]+f[i',j']<=w[i,j']+w[i',j]+f[i',y-1]+f[i,z-1]+f[z,j]+f[y,j']f[i,j]+f[i′,j′]<=w[i,j′]+w[i′,j]+f[i′,y−1]+f[i,z−1]+f[z,j]+f[y,j′]
因为f[i,j′]+f[i′,j]=w[i,j′]+w[i′,j]+f[i′,y−1]+f[i,z−1]+f[y,j]+f[z,j′]f[i,j']+f[i',j]=w[i,j']+w[i',j]+f[i',y-1]+f[i,z-1]+f[y,j]+f[z,j']f[i,j′]+f[i′,j]=w[i,j′]+w[i′,j]+f[i′,y−1]+f[i,z−1]+f[y,j]+f[z,j′]
所以,要证f[i,j′]+f[i′j]>=f[i,j]+f[i′,j′]f[i,j']+f[i'j]>=f[i,j]+f[i',j']f[i,j′]+f[i′j]>=f[i,j]+f[i′,j′],就要证f[z,j]+f[y,j′]<=f[y,j]+f[z,j′]f[z,j]+f[y,j']<=f[y,j]+f[z,j']f[z,j]+f[y,j′]<=f[y,j]+f[z,j′]。
因为i<z≤y≤ji<z≤y≤ji<z≤y≤j,所以区间的长度会在不断地迭代中越变越小,最后归于前面的几种情况。 -
情形2.1,i<y≤z≤ji<y≤z≤ji<y≤z≤j。证明过程略。
综上所述,f[i,j]f[i,j]f[i,j]满足四边形不等式。
事实上,对于任意f[I,j]=maxf[I,k]+f[k+1,j]+w[I,j]i<=k<jf[I,j]=max{f[I,k]+f[k+1,j]+w[I,j]}{i<=k<j}f[I,j]=maxf[I,k]+f[k+1,j]+w[I,j]i<=k<j,如果w满足四边形不等式,那么f也满足四边形不等式。
前面千辛万苦证明了f满足四边形不等式,那么它有什么用呢?
之所以要证明四边形不等式,就是为了证明f满足决策单调性,即:
s[i,j−1]≤s[i,j]≤s[i+1,j],i≤js[i,j-1]≤s[i,j]≤s[i+1,j], i≤js[i,j−1]≤s[i,j]≤s[i+1,j],i≤j
其中,s[I,j]s[I,j]s[I,j]为区间[I,j][I,j][I,j]上的最优决策。
当i=ji=ji=j时,单调性显然成立。因此下面只讨论i<ji<ji<j的情形。由于对称性,只要证明s[i,j]≤s[i,j+1]s[i,j]≤s[i,j+1]s[i,j]≤s[i,j+1]。
令fk[i,j]=f[i,k−1]+f[k,j]+w[i,j]fk[i,j]=f[i,k-1]+f[k,j]+w[i,j]fk[i,j]=f[i,k−1]+f[k,j]+w[i,j]。要证明s[i,j]≤s[i,j+1]s[i,j]≤s[i,j+1]s[i,j]≤s[i,j+1],只要证明对于所有i<k≤k’≤ji<k≤k’≤ji<k≤k’≤j且fk’[i,j]≤fk[i,j]fk’[i,j]≤fk[i,j]fk’[i,j]≤fk[i,j],有:fk’[i,j+1]≤fk[i,j+1]fk’[i,j+1]≤fk[i,j+1]fk’[i,j+1]≤fk[i,j+1]。
事实上,我们可以证明一个更强的不等式
fk[i,j]−fk’[i,j]≤fk[i,j+1]−fk’[i,j+1]fk[i,j]-fk’[i,j]≤fk[i,j+1]-fk’[i,j+1]fk[i,j]−fk’[i,j]≤fk[i,j+1]−fk’[i,j+1]
也就是:fk[i,j]+fk’[i,j+1]≤fk[i,j+1]+fk’[i,j]fk[i,j]+fk’[i,j+1]≤fk[i,j+1]+fk’[i,j]fk[i,j]+fk’[i,j+1]≤fk[i,j+1]+fk’[i,j]
利用递推定义式将其展开整理可得:f[k,j]+f[k’,j+1]≤f[k’,j]+f[k,j+1]f[k,j]+f[k’,j+1]≤f[k’,j]+f[k,j+1]f[k,j]+f[k’,j+1]≤f[k’,j]+f[k,j+1],这正是k≤k’≤j<j+1k≤k’≤j<j+1k≤k’≤j<j+1时的四边形不等式
综上所述,当www满足四边形不等式时,函数s[i,j]s[i,j]s[i,j]具有单调性。
因此,我们可以得到一个更优化的转移方程
上述方法利用四边形不等式推出最优决策的单调性,从而减少每个状态转移的状态数,降低算法的时间复杂度。
这样这道题就解决了。
一般的,对于状态转移方程形如:f[i,j]=optf[i,k]+f[k+1,j]+w[i,j],i<jf[i , j] = opt{f[i , k] + f[k+1 , j]} + w[i , j], i < jf[i,j]=optf[i,k]+f[k+1,j]+w[i,j],i<j的动规,若w满足四边形不等式,则f满足四边形不等式,则f有决策单调性。
Code
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using std::max;
using std::min;
const int maxn=300;
int base[maxn];
int dp[maxn][maxn],s[maxn][maxn];
int MAX[maxn][maxn];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&base[i]);
base[i+n]=base[i];
}
for(int i=1;i<=n*2;i++)
base[i]+=base[i-1];
for(int i=1;i<=n*2;i++)
s[i][i]=i;
for(int l=2;l<=n;l++)
for(int i=1;i<=(n*2)-l+1;i++)
{
int j=i+l-1;
int x=s[i][j-1],y=s[i+1][j];
dp[i][j]=0x7fffffff;
MAX[i][j]=max(MAX[i][j-1],MAX[i+1][j])+base[j]-base[i-1];//最大值不满足四边形不等式,但最有决策一定出现在端点处
for(int k=x;k<=y;k++)
if(dp[i][k]+dp[k+1][j]+base[j]-base[i-1]<dp[i][j])
{
dp[i][j]=dp[i][k]+dp[k+1][j]+base[j]-base[i-1];
s[i][j]=k;
}
}
int ansMin=0x7fffffff,ansMax=0;
for(int i=1;i<n;i++)
ansMin=min(ansMin,dp[i][i+n-1]),
ansMax=max(ansMax,MAX[i][i+n-1]);
printf("%d\n%d",ansMin,ansMax);
return 0;
}
hdu4632 **
思路:
大致题意是给定一个字符串,求回文子序列个数,最后的答案要%10007,要求一个n^2的做法
首先定义f数组f[l][r]f[l][r]f[l][r]表示l rl~rl r区间的回文子序列个数,f[i][i]=1f[i][i]=1f[i][i]=1;
显然 根据容斥原理 :f[l][r]=f[l][r−1]+f[l+1][r]−f[l+1][r−1]f[l][r]=f[l][r-1]+f[l+1][r]-f[l+1][r-1]f[l][r]=f[l][r−1]+f[l+1][r]−f[l+1][r−1] (因为中间的个数会算两遍);
然后,我们考虑s[l]==s[r]s[l]==s[r]s[l]==s[r]的情况,如果这两个位置相等,那么l+1 r−1l+1 ~ r-1l+1 r−1这个区间的所有子序列,都可以加入l和r这两个元素,构成一个新的回文子序列,除此之外 l和r这两个元素也可以构成一个回文子序列
那么 if(s[l]==s[r])f[l][r]+=f[l+1][r−1]+1if (s[l]==s[r]) f[l][r]+=f[l+1][r-1]+1if(s[l]==s[r])f[l][r]+=f[l+1][r−1]+1;
Attention
做减法的时候,可能会有负数,为了使%不出现问题,我们需要先加mod再%mo
Code:
#inclue<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define mod 10007
int dp[1005][1005];
char s[1005]
int main()
{
int t,cas=1;
cin>>t;
while(t--)
{
cin>>s;
int len=strlen(s);
memset(dp,0,sizeof dp);
for(int i=0;i<len;i++)
dp[i][i]=1;
for(int j=0;j<len;j++)
{
for(int i=j-1;i>=0;i--)
{
dp[i][j]=(dp[i][j-1]+dp[i+1][j]-dp[i+1][j-1]+mod)%mod;
if(s[i]==s[j])
dp[i][j]=(dp[i][j]+dp[i+1][j-1]+1)%mod;
}
}
printf("Case %d: %d\n",cas++,dp[0][len-1]);
}
return 0;
}
hdu4745*********
题目大意:
给定一个数列,求一个最长的回文子序列
思路:
我们定义f[l][r]f[l][r]f[l][r]表示l到r区间的最长的回文子序列长度
如果s[l]==s[r]s[l]==s[r]s[l]==s[r] 则 f[l][r]=f[l+1][r−1]+2f[l][r]=f[l+1][r-1]+2f[l][r]=f[l+1][r−1]+2;
不然 f[l][r]=max(f[l+1][r],f[l][r−1])f[l][r]=max(f[l+1][r],f[l][r-1])f[l][r]=max(f[l+1][r],f[l][r−1]);
然后统计答案的时候
因为是环
由于考虑到可以从
同一个点出发,长度为n-1,+1
或者从不同的点出发,长度为n
Code:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
using namespace std;
int s[1005],dp[2005][2005];
int main(){ //一个顺时针一个逆时针,并且元素
int n,i,j,l,ans; //相同,因此相当于在环上求最长回
while(scanf("%d",&n)!=EOF&&n){ //文子序列
memset(dp,0,sizeof(dp));
for(i=1;i<=n;i++){
scanf("%d",&s[i]);
s[i+n]=s[i];
dp[i][i]=dp[i+n][i+n]=1;
}
for(l=2;l<=2*n;l++){
for(i=1;i<=2*n-l+1;i++){
j=i+l-1;
dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
if(s[i]==s[j])
dp[i][j]=max(dp[i][j],dp[i+1][j-1]+2);
}
} //当s[i]==s[j]时长度加2
ans=0;
for(i=1;i<=n;i++){ //因为是环,所以起点可能在一个点,
ans=max(ans,dp[i][i+n-1]); //这样只需求长度是n-1的再加1即可
ans=max(ans,dp[i][i+n-2]+1);
}
printf("%d\n",ans);
}
return 0;
}
hdu2476******
题目大意:
给定一个A串,一个B串,每次修改只能将一段连续区间内的字母,改成同一个字母,询问将A变成B 的最少操作次数
思路:
因为考虑到有相等的和不相等的部分,所以不能直接做
先假设A和B所有的对应位置都不相等,然后令f[l][r]f[l][r]f[l][r]表示l~r这个区间最小操作的次数
然后对一个区间l r 我们可以考虑,首先将f[l][r]=f[l+1][r]+1f[l][r]=f[l+1][r]+1f[l][r]=f[l+1][r]+1;表示需要在上一个区间的基础上,额外操作一次。然后枚举一个kkk
从l+1l+1l+1到rrr,如果b[k]==b[l]b[k]==b[l]b[k]==b[l] 那么 l这个位置 就可以在刷k的时候 刷上,而且不影响之前的区间。
既 f[l][r]=min(f[l][r],f[l][k]+f[k+1][r])f[l][r]=min(f[l][r],f[l][k]+f[k+1][r])f[l][r]=min(f[l][r],f[l][k]+f[k+1][r])
然后 我们令ans[i]ans[i]ans[i]表示A的第1到第iii 刷成和BBB相同的 需要的 最小的操作次数
若a[i]==b[i]a[i]==b[i]a[i]==b[i] 则 ans[i]=ans[i−1]ans[i]=ans[i-1]ans[i]=ans[i−1] (因为不需要刷)
else 枚举一个j 表示这个ans[i]ans[i]ans[i]从哪个答案转移过来
ans[i]=min(ans[i],ans[j]+f[j+1][i])ans[i]=min(ans[i],ans[j]+f[j+1][i])ans[i]=min(ans[i],ans[j]+f[j+1][i]);
hdu4283 ********
题目大意:
现在给定一个序列,表示每个人的不开心值D,假如他是第x个上场,那他就有(x-1)*D的不开心值,因为他等了k-1个人,你有一个类似于栈的东西,可以使用它来改变整个序列的顺序,求最少的 不开心值的和
思路:
由于修改顺序的时候,是要用类似栈的方式QWQ所以没法贪心…
这个题有一个性质,若第i个人是通过栈调整之后,第k个表演的 那么他能造成的影响 是可以比较容易计算的。
我们令f[l][r]f[l][r]f[l][r]表示 l rl~rl r这些人的最小等待时间
首先预处理一下所有人的等待时间的前缀和sum[i]sum[i]sum[i],便于计算
对于一个区间[l,r][l,r][l,r] 我们枚举第l个人是第几个出去的
kkk从l rl~rl r
假设他是第k个出去的
则 f[l][r]=min(f[l][r],f[l+1][k]+f[k+1][r]+(k−l)∗a[i]+(k−l+1)∗(sum[r]−sum[k]))f[l][r]=min(f[l][r],f[l+1][k]+f[k+1][r]+(k-l)*a[i]+(k-l+1)*(sum[r]-sum[k]))f[l][r]=min(f[l][r],f[l+1][k]+f[k+1][r]+(k−l)∗a[i]+(k−l+1)∗(sum[r]−sum[k]))
这里有几个要注意的地方:
-
是f[l+1][k]f[l+1][k]f[l+1][k] 而不是 f[l][k]f[l][k]f[l][k] 因为这里枚举的就是第lll个人的出栈顺序
-
2>(k−l)2>(k-l)2>(k−l)表示在这个人之前有几个人出去 (k−1+1)(k-1+1)(k−1+1)表示算上这个人 出去了几个人
poj2955
题目大意
给定一个只含小括号和中括号的序列,求一个最长的合法序列。
其中() [] ()[] (()) 等 是合法的
)( (] 等 是不合法的
思路
由于两个本身合法的序列拼在一起也是合法的序列,所以我们自然而然的就想到
令f[l][r]f[l][r]f[l][r]表示l rl~rl r这个区间的最长合法序列的程度
根据a[l]a[r]f[l+1][r−1]a[l] a[r] f[l+1][r-1]a[l]a[r]f[l+1][r−1]进行初始化
然后枚举断点,进行转移
这里有一个小tip就是
可以把左右括号分别变成正负的数字,便于判断
poj1651
题目大意
给定你一个序列
其中取走一个数的代价是它乘上它相邻的两个数
两头的数不可以取~
求最小代价
思路
令f[l][r]f[l][r]f[l][r]表示把l+1 r−1l+1 ~ r-1l+1 r−1的数都取走的最小代价
首先初始化f[i][i+2]f[i][i+2]f[i][i+2]这个区间
然后之后枚举长度,起始点,断点,进行转移即可
f[l][r]=min(f[l][r],f[l][k]+f[k][r]+a[l]∗a[k]∗a[r]f[l][r]=min(f[l][r],f[l][k]+f[k][r]+a[l]*a[k]*a[r]f[l][r]=min(f[l][r],f[l][k]+f[k][r]+a[l]∗a[k]∗a[r];
本文深入探讨区间动态规划(区间DP)的概念与应用,通过解析经典案例,如石子合并问题,详细介绍如何确定状态、初始化DP数组及进行状态转移。此外,文章还介绍了四边形不等式优化技巧,用于提高算法效率,并通过多个实战题目,如回文子序列计数、最长回文子序列求解、序列操作次数最小化等,进一步巩固区间DP的理解与运用。
520

被折叠的 条评论
为什么被折叠?



