动态规划算法零基础区间DP自学笔记
Posted karshey
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划算法零基础区间DP自学笔记相关的知识,希望对你有一定的参考价值。
一个初学者的笔记。
区间dp的两种写法和一般模板
迭代式&记忆化搜索
迭代式:
//第一维循环区间长度 第二维循环左端点(范围是右端点<=n)
for(int len=1;len<=n;len++)
for(int L=1;L+len-1<=n;L++)
R=L+len-1;//求右端点
或:
来自
for(int len = 1;len<=n;len++)//枚举长度
for(int j = 1;j+len<=n+1;j++)//枚举起点,ends<=n
int ends = j+len - 1;
for(int i = j;i<ends;i++)//枚举分割点,更新小区间最优解
dp[j][ends] = min(dp[j][ends],dp[j][i]+dp[i+1][ends]+something);
AcWing 1068. 环形石子合并(环形区间dp)
看了视频,分析一下这道题的思路:
我们有n堆石子,假设n堆石子没有合并就是n个点:
两堆石子合并了之后就是两个点连成一条边:
那么合并完是这样的,形成了一个有一个缺口的环:
于是我们可以产生一个朴素的想法:枚举缺口
但是这样会超时O(n4)。
这里提出一种优化方法:取n条长度为n的链:
取n条长度为n 的链,其实就是每两个点都缺口一次。将链画成2n形式的,如果要1 5之间断开,那其实就是1-5的链,想要1 2之间断开,那就是2-(下一个)1之间的链,以此类推。
时间复杂度O(n3).
这种方式可以做绝大多数的环形区间dp
代码:
#include<bits/stdc++.h>
using namespace std;
#define mem(a,x) memset(a,x,sizeof(a));
const int N=410;//两倍的n
int n,a[N];
int dpmax[N][N],dpmin[N][N];
int sum[N];
int main()
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
a[i+n]=a[i];
for(int i=1;i<=n*2;i++) sum[i]=sum[i-1]+a[i];
mem(dpmax,-0x3f);
mem(dpmin,0x3f);
//dp
for(int len=1;len<=n;len++)
for(int l=1;l+len-1<=n*2;l++)
int r=len+l-1;
if(l==r) dpmax[l][r]=dpmin[l][r]=0;
else
for(int k=l;k<r;k++)//不能等于r
dpmax[l][r]=max(dpmax[l][r],dpmax[l][k]+dpmax[k+1][r]+sum[r]-sum[l-1]);
dpmin[l][r]=min(dpmin[l][r],dpmin[l][k]+dpmin[k+1][r]+sum[r]-sum[l-1]);
int minn=0x3f3f3f3f,maxn=-0x3f3f3f3f;
for(int i=1;i<=n;i++)
minn=min(minn,dpmin[i][i+n-1]);
maxn=max(maxn,dpmax[i][i+n-1]);
cout<<minn<<endl<<maxn;
return 0;
AcWing 320. 能量项链(环形区间dp)
看完这个大佬的题解就可以直接冲这道题了
一个跟这道题有点像的矩阵链相乘问题
#include<bits/stdc++.h>
using namespace std;
#define mem(a,x) memset(a,x,sizeof(a));
const int N=210;
int n,a[N];
int dp[N][N];
int main()
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
a[i+n]=a[i];
mem(dp,0);
//dp
for(int len=2;len<=n+1;len++)
for(int l=1;len+l-1<=2*n;l++)
int r=l+len-1;
if(len==2) dp[l][r]=0;
else
for(int k=l+1;k<r;k++)
dp[l][r]=max(dp[l][r],dp[l][k]+dp[k][r]+a[l]*a[k]*a[r]);
int ans=-1;
for(int i=1;i<=n;i++)
ans=max(ans,dp[i][i+n]);
cout<<ans;
return 0;
AcWing 479. 加分二叉树
#include<bits/stdc++.h>
using namespace std;
#define fir(i,a,n) for(int i=a;i<=n;i++)
typedef long long ll;
const int N=30+10;
int a[N],n;
int dp[N][N];//dp[i][j]表示[i,j]区间内最大的二叉树加分
int root[N][N];//root[i][j]表示区间为[i,j]的根节点
void print(int l,int r)
if(l>r) return;
cout<<root[l][r]<<" ";
print(l,root[l][r]-1);
print(root[l][r]+1,r);
int main()
cin>>n;
fir(i,1,n) cin>>a[i];
for(int len=1;len<=n;len++)
for(int l=1;l+len-1<=n;l++)
int r=l+len-1;
if(l==r)//叶子节点
dp[l][r]=a[l];
root[l][r]=l;
else //不是叶子节点,会有左/右子树
for(int k=l;k<=r;k++)//枚举[l,r]范围内的根节点
//可能会出现左/右子树其中一个为空的情况
int left=(k==l?1:dp[l][k-1]);
int right=(k==r?1:dp[k+1][r]);
int score=left*right+a[k];
//严格大于才更新,则在相同情况下是字典序的
if(score>dp[l][r])
dp[l][r]=score;
root[l][r]=k;
cout<<dp[1][n]<<endl;
print(1,n);
return 0;
洛谷P3205 [HNOI2010]合唱队
题
题解看的是:zhaohaikun’s blog
要注意:
- dp[i][j]表示理想队形为[i,j]区间的个数,[0]表示放到左边,[1]表示放到右边。一共三维
- 初始化:dp[i][i]表示的是只有一个人的时候的方案数,只有一个人i,则之前没有别的人,那么到左边和到右边都是一样的,方案数是1,不能初始化两次(即同时初始化[0]和[1])
- 进来的人放在左边的话,就是i,比前面的人小;前面的人有两种放法,分别是i+1或j;之前的区间[i,j]就会变成[i+1,j],放在右边同理
- 输出的结果是左右加载一起的结果
代码:
#include<bits/stdc++.h>
using namespace std;
#define fir(i,a,n) for(int i=a;i<=n;i++)
typedef long long ll;
const int N=1e3+10;
const int MOD=19650827;
int a[N],n;
int dp[N][N][2];//dp[i][j]表示理想队形为[i,j]区间的个数
//[0]表示放到左边,[1]表示放到右边
int main()
cin>>n;
fir(i,1,n) cin>>a[i];
//初始化
fir(i,1,n)
dp[i][i][0]=1;
// dp[i][i][1]=1;
//只有一个人的时候方案只有一种,所以只能一个1
//不管是左1还是右1都可以 但是只能是一个
for(int len=1;len<=n;len++)//枚举区间长度
for(int l=1;l+len-1<=n;l++)//枚举左端点,注意右端点不能出界
int r=l+len-1;//右端点
//放到左边就是比前一个数小的两种情况:
// 前一个数在左边 即i+1
// 前一个数在右边 即r
if(a[l]<a[l+1]) dp[l][r][0]+=dp[l+1][r][0];
if(a[l]<a[r]) dp[l][r][0]+=dp[l+1][r][1];
//同理,放在右边就是比前一个数字大:
//前一个数字在最左边l 或右边r-1
if(a[r]>a[l]) dp[l][r][1]+=dp[l][r-1][0];
if(a[r]>a[r-1]) dp[l][r][1]+=dp[l][r-1][1];
dp[l][r][0]%=MOD;
dp[l][r][1]%=MOD;
cout<<(dp[1][n][0]+dp[1][n][1])%MOD;
return 0;
P4302 [SCOI2003]字符串折叠
枚举的思想+区间dp:
#include<bits/stdc++.h>
using namespace std;
#define fir(i,a,n) for(int i=a;i<=n;i++)
typedef long long ll;
const int N=1e2+10;
int dp[N][N];//dp[i][j]表示从i到j的最小折叠长度
string a;
int zhedie(int l,int r,int len) //开头 结尾 长度
for(int i=l;i<=r;i++)
//算出i到起点的距离
if(a[i]!=a[(i-l)%len+l]) return 0;
return 1;
int main()
cin>>a;
a=" "+a;
int n=a.size()-1;
//初始化 自己到自己的折叠就是不折叠——1
memset(dp,0x3f,sizeof(dp));
fir(i,1,n) dp[i][i]=1;
for(int len=2;len<=n;len++)//枚举选中的折叠长度
for(int i=1;i+len-1<=n;i++) //枚举开头
int j=i+len-1;//算出结尾
//len从2开始 ij不会重叠
for(int k=i;k<j;k++)//作为跳板,先合并
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
for(int k=i;k<j;k++) //从哪里断
int lenn=k-i+1;
if(zhedie(i,j,lenn))
if(len%lenn) continue;//无法整除的必然无法合并
int temp=0;
if(len/lenn<10) temp=1;
else以上是关于动态规划算法零基础区间DP自学笔记的主要内容,如果未能解决你的问题,请参考以下文章