顾名思义,就是解决一些区间内最优值的问题,通常的时间复杂度为 n 2 n^2 n2 或者 n 3 n^3 n3
首先确定状态
初始化长度为1(or 2,3…具体因题而异)的dp数组的值
然后枚举区间长度,枚举区间的起始点,(有的题目还需要枚举断点) 由小区间转移到大区间。
最后 d p [ 1 ] [ n ] dp[1][n] dp[1][n]往往就是答案。
贪心~很明显是不行的
好的,那我们还是开始想dp吧。区间dp常用的一个状态就是dp[i][j]表示i~j这个区间的最优值是多少?
我们可以看出题目中这个合并过程,很像是区间dp的一种合并,也就是说 d p [ i ] [ j ] dp[i][j] dp[i][j]可以由 d p [ i ] [ k ] dp[i][k] dp[i][k]和 d p [ k + 1 ] [ j ] dp[k+1][j] dp[k+1][j]转移过
那么我们自然而然的想到要枚举上一次合并是在哪个位置,也就是断点k,然后进行转
不过,由于这个题目的特殊限制,我们需要记录g[i][j]表示i到j这个区间有多少石子,来进行辅助转移
f [ i ] [ j ] = m i n ( 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 就可以啦
#include
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)表示 d p ( i , j ) dp(i,j) dp(i,j) 取得最优值时对应的下标(即 i ≤ k ≤ j i≤k≤j i≤k≤j 时,k 处的 dp 值最大,则 s ( i , j ) = k s(i,j)=k s(i,j)=k此时有如下定理
2、假如 d p ( 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 j i~j i 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 ] = m i n f [ I , k ] + f [ k + 1 , j ] + w [ I , j ] i < = k < j f[I,j]=min{f[I,k]+f[k+1,j]+w[I,j]}{i<=k
我们利用数学归纳法对四边形不等式中的区间长度进行归纳。
证明如下:
当i=i’或j=j’时,不等式显然成立。由此可知,当l≤1时,函数f满足四边形不等式。
下面分两种情形进行归纳证明:
情形1: i < i ’ = j < j ’ ii<i’=j<j’
在这种情形下,四边形不等式简化为如下的不等式: f [ i , j ] + f [ j , j ’ ] ≤ f [ i , j ’ ] + 0 f[i,j]+f[j,j’] ≤f[i,j’]+0 f[i,j]+f[j,j’]≤f[i,j’]+0,设 k = m a x p ∣ 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 ≤ j k≤j k≤j或 k > j k>j k>j。下面只讨论 k ≤ j k≤j k≤j, k > j k>j k>j 的情况是类似的。
情形1.1: k ≤ j k≤j k≤j,此时:
情形1.2: k > j k>j k>j,略去
情形2: i < i ’ < j < j ’ ii<i’<j<j’
设 y = m a x p ∣ 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 ≤ y z≤y z≤y或 z > y z>y z>y。下面只讨论 z ≤ y z≤y z≤y, z > y z>y z>y的情况是类似的。
情形2.1, i < z ≤ y ≤ j i
显然的,我们有:
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 ≤ j i
情形2.1, i < y ≤ z ≤ j i
综上所述, f [ i , j ] f[i,j] f[i,j]满足四边形不等式。
事实上,对于任意 f [ I , j ] = m a x f [ I , k ] + f [ k + 1 , j ] + w [ I , j ] i < = k < j f[I,j]=max{f[I,k]+f[k+1,j]+w[I,j]}{i<=k
前面千辛万苦证明了f满足四边形不等式,那么它有什么用呢?
之所以要证明四边形不等式,就是为了证明f满足决策单调性,即:
s [ i , j − 1 ] ≤ s [ i , j ] ≤ s [ i + 1 , j ] , i ≤ j s[i,j-1]≤s[i,j]≤s[i+1,j], i≤j s[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 = j i=j i=j时,单调性显然成立。因此下面只讨论 i < j i
令 f k [ 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 ’ ≤ j i
事实上,我们可以证明一个更强的不等式
f k [ i , j ] − f k ’ [ i , j ] ≤ f k [ i , j + 1 ] − f k ’ [ 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]
也就是: f k [ i , j ] + f k ’ [ i , j + 1 ] ≤ f k [ i , j + 1 ] + f k ’ [ 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 + 1 k≤k’≤j
综上所述,当 w w w满足四边形不等式时,函数 s [ i , j ] s[i,j] s[i,j]具有单调性。
因此,我们可以得到一个更优化的转移方程
上述方法利用四边形不等式推出最优决策的单调性,从而减少每个状态转移的状态数,降低算法的时间复杂度。
这样这道题就解决了。
一般的,对于状态转移方程形如: f [ i , j ] = o p t f [ i , k ] + f [ k + 1 , j ] + w [ i , j ] , i < j f[i , j] = opt{f[i , k] + f[k+1 , j]} + w[i , j], i < j f[i,j]=optf[i,k]+f[k+1,j]+w[i,j],i<j的动规,若w满足四边形不等式,则f满足四边形不等式,则f有决策单调性。
#include
#include
#include
#include
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;
}
大致题意是给定一个字符串,求回文子序列个数,最后的答案要%10007,要求一个n^2的做法
首先定义f数组 f [ l ] [ r ] f[l][r] f[l][r]表示 l r l~r l r区间的回文子序列个数, f [ i ] [ i ] = 1 f[i][i]=1 f[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 − 1 l+1 ~ r-1 l+1 r−1这个区间的所有子序列,都可以加入l和r这两个元素,构成一个新的回文子序列,除此之外 l和r这两个元素也可以构成一个回文子序列
那么 i f ( s [ l ] = = s [ r ] ) f [ l ] [ r ] + = f [ l + 1 ] [ r − 1 ] + 1 if (s[l]==s[r]) f[l][r]+=f[l+1][r-1]+1 if(s[l]==s[r])f[l][r]+=f[l+1][r−1]+1;
Attention
做减法的时候,可能会有负数,为了使%不出现问题,我们需要先加mod再%mo
#inclue
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;
}
给定一个数列,求一个最长的回文子序列
我们定义 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 ] + 2 f[l][r]=f[l+1][r-1]+2 f[l][r]=f[l+1][r−1]+2;
不然 f [ l ] [ r ] = m a x ( 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
#include
#include
#include
#include
#include
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;
}
给定一个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 ] + 1 f[l][r]=f[l+1][r]+1 f[l][r]=f[l+1][r]+1;表示需要在上一个区间的基础上,额外操作一次。然后枚举一个 k k k
从 l + 1 l+1 l+1到 r r r,如果 b [ k ] = = b [ l ] b[k]==b[l] b[k]==b[l] 那么 l这个位置 就可以在刷k的时候 刷上,而且不影响之前的区间。
既 f [ l ] [ r ] = m i n ( 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])
然后 我们令 a n s [ i ] ans[i] ans[i]表示A的第1到第 i i i 刷成和 B B B相同的 需要的 最小的操作次数
若 a [ i ] = = b [ i ] a[i]==b[i] a[i]==b[i] 则 a n s [ i ] = a n s [ i − 1 ] ans[i]=ans[i-1] ans[i]=ans[i−1] (因为不需要刷)
else 枚举一个j 表示这个 a n s [ i ] ans[i] ans[i]从哪个答案转移过来
a n s [ i ] = m i n ( a n s [ i ] , a n s [ 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]);
现在给定一个序列,表示每个人的不开心值D,假如他是第x个上场,那他就有(x-1)*D的不开心值,因为他等了k-1个人,你有一个类似于栈的东西,可以使用它来改变整个序列的顺序,求最少的 不开心值的和
由于修改顺序的时候,是要用类似栈的方式QWQ所以没法贪心…
这个题有一个性质,若第i个人是通过栈调整之后,第k个表演的 那么他能造成的影响 是可以比较容易计算的。
我们令 f [ l ] [ r ] f[l][r] f[l][r]表示 l r l~r l r这些人的最小等待时间
首先预处理一下所有人的等待时间的前缀和 s u m [ i ] sum[i] sum[i],便于计算
对于一个区间 [ l , r ] [l,r] [l,r] 我们枚举第l个人是第几个出去的
k k k从 l r l~r l r
假设他是第k个出去的
则 f [ l ] [ r ] = m i n ( f [ l ] [ r ] , f [ l + 1 ] [ k ] + f [ k + 1 ] [ r ] + ( k − l ) ∗ a [ i ] + ( k − l + 1 ) ∗ ( s u m [ r ] − s u m [ 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] 因为这里枚举的就是第 l l l个人的出栈顺序
2 > ( k − l ) 2>(k-l) 2>(k−l)表示在这个人之前有几个人出去 ( k − 1 + 1 ) (k-1+1) (k−1+1)表示算上这个人 出去了几个人
给定一个只含小括号和中括号的序列,求一个最长的合法序列。
其中() [] ()[] (()) 等 是合法的
)( (] 等 是不合法的
由于两个本身合法的序列拼在一起也是合法的序列,所以我们自然而然的就想到
令 f [ l ] [ r ] f[l][r] f[l][r]表示 l r l~r l 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就是
可以把左右括号分别变成正负的数字,便于判断
给定你一个序列
其中取走一个数的代价是它乘上它相邻的两个数
两头的数不可以取~
求最小代价
令 f [ l ] [ r ] f[l][r] f[l][r]表示把 l + 1 r − 1 l+1 ~ r-1 l+1 r−1的数都取走的最小代价
首先初始化 f [ i ] [ i + 2 ] f[i][i+2] f[i][i+2]这个区间
然后之后枚举长度,起始点,断点,进行转移即可
f [ l ] [ r ] = m i n ( 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];