【5】区间类型动态规划学习笔记
前言
教练阳了,这次上的是录播课(悲
希望教练早点好起来
区间DP
区间DP,字面上讲,就是以一个区间为状态进行转移的动态规划。
在这类动态规划中,一般设状态
区间DP的题目的特征:
基本空间复杂度:
DP例题
例题
P1063 [NOIP2006 提高组] 能量项链
设状态
其中
还有一个小细节:由于是一个环,需要破环成链。可以通过开两倍内存,把第
或者看看我的这篇博客 零碎知识点整理
#include <bits/stdc++.h>
using namespace std;
int n,max1[600][600],a[20000],maxa;
int main()
{
scanf("%lld",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
a[n+i]=a[i];
}
for(int i=n*2-1;i>=0;i--)
for(int j=i+1;j<=2*n-1;j++)
for(int k=i+1;k<=j;k++)
max1[i][j]=max(max1[i][j],max1[i][k-1]+max1[k][j]+a[i]*a[k]*a[j+1]);
for(int i=0;i<n;i++)maxa=max(maxa,max1[i][i+n-1]);
printf("%d",maxa);
return 0;
}
例题
CF607B Zuma
设状态
其中
第一个转移方程中,考虑回文串的性质,如果区间
第二个转移方程中,整个区间的值可以由消除分割点左边区间的次数加上
消除分割点右边区间的次数,这样就能保证消除完。而消除分割点左边区间的次数是
#include <bits/stdc++.h>
using namespace std;
int n,f[600][600];
int a[600];
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)scanf("%d",&a[i]);
for(int i=0;i<n;i++)f[i][i]=1,f[i+1][i]=1;
for(int l=2;l<=n;l++)
for(int i=0;i+l-1<n;i++)
{
int j=i+l-1;
f[i][j]=99999999;
if(a[i]==a[j])f[i][j]=min(f[i][j],f[i+1][j-1]);
for(int k=i+1;k<=j;k++)
f[i][j]=min(f[i][j],f[i][k-1]+f[k][j]);
}
printf("%d",f[0][n-1]);
return 0;
}
例题
P1005 [NOIP2007 提高组] 矩阵取数游戏
首先,由于各行独立,可以各行分别计算。
设状态
其中
注意:每行取数的得分
由于数字太大,需要开 __int128
#include <bits/stdc++.h>
using namespace std;
int n,m;
__int128 ans,a[20000],f[1020][1020];
inline __int128 read()
{
__int128 x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void print(__int128 x)
{
if(x>9)print(x/10);
putchar(x%10+'0');
}
int main()
{
scanf("%d%d",&n,&m);
for(int k=0;k<n;k++)
{
for(int i=0;i<m;i++)
a[i]=read();
for(int i=0;i<=m;i++)
for(int j=0;j<=m;j++)
f[i][j]=0;
for(int len=1;len<=m;len++)
for(int l=0;l<=m-len;l++)
{
int r=l+len-1;
f[l][r]=2*max(f[l+1][r]+a[l],f[l][r-1]+a[r]);
}
ans+=f[0][m-1];
}
print(ans);
return 0;
}
例题
2183: 【一本通提高区间类动态规划】分离与合体(站外题,提供题面展示)
时间限制:
Judge Mode:Std IO
题目描述
经过在机房里数日的切磋,LYD从杜神牛那里学会了分离与合体,出关前,杜神牛给了他一个测试......
杜神牛造了
一开始LYD可以选择从
但是LYD不能就这么分成
(合并后所在区间的最左端区域和最右端区域里金钥匙的价值之和) 乘 (之前分离的时候所在区域的金钥匙价值)。
例如:LYD曾经在
LYD请你编程求出最终可以获得的总价值最大是多少。并按照分离阶段从前到后,区域从左向右的顺序,输出发生分离的区域编号 (例如:先打印
注意:若有多种方案,选择分离区域尽量靠左的方案(也可以理解为输出字典序最小的)。
输入
第一行:正整数
第二行:
保证答案及运算过程中不超出longint范围。
输出
第一行一个数,即获得的最大价值
第二行按照分离阶段从前到后,区域从左向右的顺序,输出发生分离的区域编号,中间用一个空格隔开,若有多种方案,选择分离区域尽量靠左的方案(也可以理解为输出字典序最小的)。
输入输出样例
样例输入
7
1 2 3 4 5 6 7
样例输出
238
1 2 3 4 5 6
提示
对于
对于
对于
类似能量项链(不用破环成链),易得转移方程:
注意需要横向输出路径,可以记录分割点
#include <bits/stdc++.h>
using namespace std;
int n,max1[600][600],a[20000],maxa,pre[600][600],mi,mj;
void out(int ii,int jj)
{
int fx[100000],fy[100000],head=0,tail=0;
fx[++head]=ii;
fy[++tail]=jj;
while(head<=tail)
{
int i=fx[head];int j=fy[head];
if(pre[i][j]!=0)
{
printf("%d ",pre[i][j]);
fx[++tail]=i;fy[tail]=pre[i][j]-1;
fx[++tail]=pre[i][j];fy[tail]=j;
}
head++;
}
}
int main()
{
scanf("%lld",&n);
for(int i=0;i<n;i++)scanf("%d",&a[i]);
for(int i=n-1;i>=0;i--)
for(int j=i+1;j<=n-1;j++)
for(int k=i+1;k<=j;k++)
if(max1[i][k-1]+max1[k][j]+(a[i]+a[j])*a[k-1]>max1[i][j])
{
max1[i][j]=max1[i][k-1]+max1[k][j]+(a[i]+a[j])*a[k-1];
pre[i][j]=k;
}
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(i!=j&&max1[i][j]>maxa)maxa=max1[i][j],mi=i,mj=j;
printf("%d\n",maxa);
out(mi,mj);
return 0;
}
后记
讲得非常好的一篇区间DP题解
扩展博客:区间DP