区间dp笔记
引:石子合并
设有
若最初的第
对应到动态规划中,就意味着两个长度较小的区间上的信息向一个更长的区间发生了转移,划分点
设
初值:
目标:
我们发现
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;i++){
f[i][i]=0;
sum[i]=sum[i-1]+a[i];
}
for(int len=2;len<=n;len++){
for(int l=1,r=l+len-1;r<=n;l++,r++){
for(int k=l;k<r;k++)
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]);
f[l][r]+=sum[r]-sum[l-1];
}
}
例1:能量项链(NOIP)
题目描述
在 Mars 星球上,每个 Mars 人都随身佩带着一串能量项链。在项链上有
需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。
例如:设
这一串项链可以得到最优值的一个聚合顺序所释放的总能量为:
思路
考虑区间
很容易写出转移方程:
现在唯一有点棘手的问题是本题是求环上的最大值,最朴素的做法就是枚举每个断点进行
代码
#include<bits/stdc++.h>
using namespace std;
const int N=410;
int n,a[N],f[N][N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=n+1;i<=2*n;i++)a[i]=a[i-n];
for(int i=1;i<=2*n;i++)f[i][2]=a[i]*a[i+1]*a[i+2];
for(int j=3;j<=2*n;j++){
for(int i=1;i<=2*n;i++){
if(i+j-1>=2*n)break;
for(int k=1;k<j;k++){
f[i][j]=max(f[i][j],f[i][k]+f[i+k][j-k]+a[i]*a[i+j]*a[i+k]);
}
}
}
int ans=0;
for(int i=1;i<=2*n;i++)ans=max(ans,f[i][n]);
printf("%d\n",ans);
return 0;
}
例2:【USACO3.3.5】A Game游戏 IOI'96
题目描述
有如下一个双人游戏:
编一个执行最优策略的程序,最优策略就是使玩家在与最好的对手对弈时,能得到的在当前情况下最大的可能的总分的策略。你的程序要始终为第二位玩家执行最优策略。
思路
状态只有
假设当前状态
所以
所以
根据
代码
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i],f[i][i]=a[i];
for(int len=1;len<n;len++){
for(int i=1;i+len<=n;i++){
int j=len+i;
f[i][j]=max(sum[j]-sum[i-1]-f[i+1][j],sum[j]-sum[i-1]-f[i][j-1]);
}
}
printf("%d %d\n",f[1][n],sum[n]-f[1][n]);
这道题妙在状态转移,本质上是类似于取反的关系,现在先手选,那么剩下的区间先手就变为后手,后手变为先手,以此类推。
例3:做错的作业
题目大意
给定一个长度为
思路分析
此题虽然很简单,但它分析的思路成为了以后这种括号序列的套路,如果凭借自己的思考在高强度的考场上大概率是想不出来的。
很明显,对于原括号序列的每个括号最后它要么是单独添加一个括号,要么是与之前的括号进行匹配。
设状态
综上,转移方程为:
代码
for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)f[i][j]=9999999999;
for(int i=1;i<=n;i++)f[i][i]=1;
for(int len=1;len<n;len++){
for(int i=1;i+len<=n;i++){
int j=i+len;
f[i][j]=min(f[i][j],f[i][j-1]+1);
for(int k=i;k<=j-1;k++){
if((s[k]=='('&&s[j]==')')||(s[k]=='['&&s[j]==']')||(s[k]=='<'&&s[j]=='>')||(s[k]=='{'&&s[j]=='}')){
f[i][j]=min(f[i][j],f[i][k-1]+f[k+1][j-1]);
}
}
}
}
printf("%d\n",f[1][n]);
例4:【CQOI2007】涂色
题目描述
假设你有一条长度为
每次你可以把一段连续的木板涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色。例如第一次把木板涂成
用尽量少的涂色次数达到目标。
思路
典型的区间
很容易写出状态转移方程:
代码
for(int i=1;i<=n;i++)f[i][i]=1;
for(int len=1;len<n;len++){
for(int l=1;len+l<=n;l++){
int r=len+l;
if(a[l]==a[r])f[l][r]=min(f[l][r-1],f[l+1][r]);
for(int k=l;k<r;k++){
f[l][r]=min(f[l][k]+f[k+1][r],f[l][r]);
}
}
}
printf("%d\n",f[1][n]);
例5:凸多边形的三角剖分
题目描述
给定一具有
思路
考虑区间
Part 1 状态设计
设状态
Part 2 状态转移
结合题目特性,我们发现只会用一种转移,就是合并两个更小的凸多边形,那么就写出状态转移方程。
代码
注意开long long
#include<bits/stdc++.h>
using namespace std;
const int N=305;
int n,t;
struct node{
int x,y;
}a[N];
int f[N][N],dis[N][N];
int dist(int i,int j){
if(abs(j-i)==1)return 0;
return abs(a[i].x+a[j].x)*abs(a[i].y+a[j].y)%t;
}
int main(){
scanf("%d %d",&n,&t);
for(int i=1;i<=n;i++)scanf("%d %d",&a[i].x,&a[i].y);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)f[i][j]=0x3f3f3f3f3f3f;
if(i+1<=n)f[i][i+1]=0;
else f[i][1]=0;
}
for(int len=2;len<n;len++){
for(int i=1;i+len<=n;i++){
int j=i+len;
for(int k=i+1;k<j;k++){
f[i][j]=min(f[i][j],f[i][k]+f[k][j]+dist(i,k)+dist(j,k));
}
}
}
printf("%d\n",f[1][n]);
return 0;
}
想必到现在读者一定会对区间
例6:字符串折叠
题目描述
折叠的定义如下:
-
一个字符串可以看成它自身的折叠。记作S =S
-
X(S)是X(X>1)个S连接在一起的串的折叠。记作X(S) = SSSS…S(X个S)。
-
如果A = A’, B=B’,则AB = A’B’
例如,因为3(A) = AAA, 2(B) = BB,所以3(A)C2(B) = AAACBB,而2(3(A)C)2(B)=AAACAAACBB给一个字符串,求它的最短折叠。 例如AAAAAAAAAABABABCCD的最短折叠为:9(A)3(AB)CCD。
思路
这道题也是一样先设状态
考虑转移,第一种转移即为不加括号和数字,区间
第二种转移为加括号和数字,比如一段小区间
Code
#include<bits/stdc++.h>
using namespace std;
const int N=200;
string s;
int f[N][N],num[N];
int main(){
cin>>s;
int n=s.length();
s=" "+s;
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;i++)f[i][i]=1;
for(int i=1;i<=9;i++)num[i]=1;
for(int i=10;i<=99;i++)num[i]=2;
num[100]=3;
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++){
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
if((len+1)%(k-i+1)==0){
bool flag=true;
int lenn=k-i+1;
for(int h=i;h<=j;h++)
if(s[h]!=s[(h-i)%lenn+i]){
flag=false;
break;
}
if(flag)f[i][j]=min(f[i][j],2+num[(len+1)/(k-i+1)]+f[i][k]);
}
}
}
}
printf("%d\n",f[1][n]);
return 0;
}
例7:加分二叉树(NOIP)
思路
和前面状态设计差不多,设
现在考虑怎么求具体方案,再开一个数组
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=35;
int f[N][N],n,a[N],root[N][N];
void print(int l,int r){
if(l>r)return;
printf("%lld ",root[l][r]);
if(l==r)return;
print(l,root[l][r]-1);print(root[l][r]+1,r);
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]),f[i][i]=a[i],root[i][i]=i;
for(int len=1;len<n;len++){
for(int i=1;i+len<=n;i++){
int j=i+len;
f[i][j]=f[i][i]+f[i+1][j];root[i][j]=i;
for(int k=i+1;k<j;k++){
if(f[i][j]<f[i][k-1]*f[k+1][j]+f[k][k]){
f[i][j]=f[i][k-1]*f[k+1][j]+f[k][k];
root[i][j]=k;
}
}
}
}
printf("%lld\n",f[1][n]);
print(1,n);
return 0;
}
例8:[SCOI2007] 压缩
思路
此题最大的问题是在有无
现在考虑转移:
设
tip!
进行此种转移的前提必须是
剩余是常规操作:
#include<bits/stdc++.h>
using namespace std;
const int N=55;
int f[N][N][2];
char a[N];
int n;
bool check(int l,int r){
int len=(r-l+1)>>1;
if((r-l+1)%2==1)return false;
for(int i=l,j=l+len;i<l+len;i++,j++){
if(a[i]!=a[j])return false;
}
return true;
}
int main(){
scanf("%s",(a+1));
n=strlen((a+1));
for(int i=1;i<=n;i++){
f[i][i][0]=1;
f[i][i][1]=2;
}
for(int len=2;len<=n;len++){
for(int i=1,j=i+len-1;j<=n;i++,j++){
f[i][j][0]=f[i][j][1]=0x3f3f3f3f;
if(check(i,j)){
int mid=(i+j)>>1;
f[i][j][0]=min(f[i][j][0],f[i][mid][0]+1);
}
for(int k=i;k<j;k++){
f[i][j][0]=min(f[i][j][0],f[i][k][0]+j-k);
f[i][j][1]=min(f[i][j][1],min(f[i][k][1],f[i][k][0])+min(f[k+1][j][0],f[k+1][j][1])+1);
}
}
}
printf("%d\n",min(f[1][n][0],f[1][n][1]));
return 0;
}
例9:【JXOI2018】守卫
可以证明,在一个区间中,无法被右端点看到的每一个点构成的区间一定是无法被这个区间外除右端点的右边一个端点之外的点看到
因此状态可以直接用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】