区间DP
整理区间DP例题以及对区间DP理解
区间DP,即合并类动态规划,往往以区间为阶段进行划分
注:有的决策是左右端点向左向右扩展
在解决区间合并问题时:
1.以区间长度为阶段
2.以左右端点为状态
3.以区间划分为决策
另一种写法以左端点为阶段,右端点为状态直接枚举决策
这种写法是可行的
还有是要注意写法:
伪代码模板
void DP(){
for(阶段){
for(状态){
for(决策){
状态转移方程;
}
}
}
for(阶段){
答案=最优决策;
}
}
例题:
石子合并:
1.线性:
这里就直接套上述区间dp设计模板即可:
1.以区间长度为阶段:区间长度从2到n;
2.以左右端点为状态:枚举左端点求右端点即可确定状态;
3.以断点枚举为决策:从左端点枚举断点到右端点,推出状态转移方程
显然:如果以k为断点,那么最优状态无非就是在k断开时最优解与不在k断开时的最优解两者中更优的那一个
因此,我们得出状态转移方程:
code
线性
#include <bits/stdc++.h>
using namespace std;
int n,maxn=-2147483647,minn=2147483647;
const int p=1010;
int u[p][p],d[p][p],f[p],sum[p];
void in(){
memset(d,0x7f,sizeof(d));
memset(u,0xcf,sizeof(u));
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&f[i]);
sum[i]=sum[i-1]+f[i];
u[i][i]=0;
d[i][i]=0;
}
}
void work(){
for(int len=2;len<=n;len++){
for(int l=1;l<=n;l++){
int r=l+len-1;
for(int k=l;k<r;k++){
u[l][r]=max(u[l][r],u[l][k]+u[k+1][r]);
d[l][r]=min(d[l][r],d[l][k]+d[k+1][r]);
}
u[l][r]+=sum[r]-sum[l-1];
d[l][r]+=sum[r]-sum[l-1];
}
}
minn=d[1][n];
maxn=u[1][n];
}
void out(){
printf("%d\n%d\n",minn,maxn);
/* for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
printf("%d ",u[i][j]);
}
printf("\n");
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
printf("%d ",d[i][j]);
}
printf("\n");
}*/
}
int main(){
in();
work();
out();
return 0;
}
2.环形(NOI1995)
与线性不同的是环形,那么,环形的处理办法?
化环成链:即把环形的数据结构通过复制并连接在原数据结构后边,实现环中每一个合法的链(可能不是最大)在新数据结构中都存在一段连续的数列与之相对应的方法
这种拆环的手段在环形区间DP中是一种常用的处理手段
不过要注意一个小细节,就是拆环以后的最大合法长度是n,但是数据的长度是2n,因此左端点的枚举也要到2n-1-len的位置
剩下的部分我们已经将环拆成链了,因此与线性的是一样的了
code
环形
#include <bits/stdc++.h>
using namespace std;
int n,maxn,minn=0x7f7f7f7f;
const int p=2e3;
int u[p][p],d[p][p],f[p],sum[p];
void in(){
memset(d,0x7f,sizeof(d));
memset(u,0xcf,sizeof(u));
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&f[i]);
f[i+n]=f[i];
}
}
void change(){
for(int i=1;i<=2*n;i++){
sum[i]=sum[i-1]+f[i];
u[i][i]=0;
d[i][i]=0;
}
}
void work(){
for(int len=2;len<=n;len++){
for(int i=1;i<=2*n-len;i++){
int j=i+len-1;
for(int k=i;k<j;k++){
u[i][j]=max(u[i][j],u[i][k]+u[k+1][j]+sum[j]-sum[i-1]);
d[i][j]=min(d[i][j],d[i][k]+d[k+1][j]+sum[j]-sum[i-1]);
}
}
}
for(int i=1;i<=n;i++){
maxn=max(maxn,u[i][i+n-1]);
minn=min(minn,d[i][i+n-1]);
}
}
void out(){
printf("%d\n%d\n",minn,maxn);
/* for(int i=1;i<=2*n;i++){
for(int j=1;j<=2*n;j++){
cout<<u[i][j]<<" ";
}
cout<<endl;
}
cout<<endl;
for(int i=1;i<=2*n;i++){
for(int j=1;j<=2*n;j++){
cout<<d[i][j]<<" ";
}
cout<<endl;
}*/
}
int main(){
in();
change();
work();
out();
return 0;
}
3.优化
1.四边形不等式优化DP:
四边形不等式:由于不好整图片就直接上OIwiki的链接了
该优化可以将原本
但是由于空间复杂度也是
四边形不等式优化DP
#include <bits/stdc++.h>
using namespace std;
int n,maxn;
const int p=5e3;
int u[p][p],f[p],sum[p];
void in(){
memset(u,0xcf,sizeof(u));
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&f[i]);
f[i+n]=f[i];
}
}
void change(){
for(int i=1;i<=2*n;i++){
sum[i]=sum[i-1]+f[i];
u[i][i]=0;
}
}
void work(){
for(int d=2;d<=n;d++){
for(int i=1,j;(j=i+d-1)<=2*n;i++){
u[i][j]=max(u[i][j-1],u[i+1][j])+sum[j]-sum[i-1];
}
}
for(int i=1;i<=n;i++){
maxn=max(maxn,u[i][i+n-1]);
}
}
void out(){
printf("%d\n",maxn);
}
int main(){
in();
change();
work();
out();
return 0;
}
2.专门算法(SDOI2008)
GarsiaWachs算法
我专门查了这个算法然而只查到写法没查到证明本蒟蒻fw一个不会证略了
这个算法是用来求线性石子合并类型中的最大值,可以把时间复杂度干到
然而能解决的类型实在少,谁叫这玩意
贴code跑路
GarsiaWachs
#include <cstdio>
using namespace std;
#define maxn 50010
typedef long long ll;
ll st[maxn];
int n,t;
ll ans;
inline void combine(int x){
ll tmp = st[x] + st[x - 1];
ans += tmp;
for(int i = x;i < t - 1;i++){
st[i] = st[i + 1];
}
t--;
int j = 0;
for(j = x - 1;j > 0 && st[j - 1] < tmp;j--){
st[j] = st[j - 1];
}
st[j] = tmp;
while(j >= 2 && st[j] > st[j - 2]){
int d = t - j;
combine(j - 1);
j = t - d;
}
}
int main() {
//ios::sync_with_stdio(0); cin.tie(0);
int n;scanf("%d",&n);
for(int i = 0;i < n;i++)scanf("%lld",&st[i]);
t = 1;ans = 0;
for(int i = 1;i < n;i++){
st[t++] = st[i];
while(t >= 3 && st[t - 3] <= st[t - 1]){
combine(t - 2);
}
}
while(t > 1)combine(t - 1);
printf("%lld\n",ans);
return 0;
}
能量项链/矩阵连乘/三角划分:
这类是相乘的并且首尾相接
但是还是区间合并类DP
因此还是三件套:
区间长——阶段,左右端点——状态,断点——决策
1.矩阵连乘(线性)
还是以断点列状态转移方程
断点左:左端点到断点的最优状态
断点右:断点右一到右端点的最优状态
处理断点:左端合并完毕剩下左端点头,断点的尾(即断点右一)
同理:右端合并完毕后剩下断点右一的头,右端点的尾
合并后的总运算量:断点计算量+断点左,右的最优计算量
与不断开相比,最优运算量应是两者较小值,因此:
状态转移方程:
i为左端点,j为右端点,k为断点,
细节处理:要把f数组初始化为0x3f3f3f3f,以得出最小值
code
矩阵连乘
#include <bits/stdc++.h>
using namespace std;
const int p=1e3;
int n,ans,f[p][p],a[p];
void in(){
memset(f,0x7f,sizeof(f));
scanf("%d",&n);
for(int i=1;i<=n+1;i++){
scanf("%d",&a[i]);
f[i][i]=0;
}
}
void work(){
for(int i=2;i<=n+1;i++){
for(int j=i-1;i-j<n&&j;j--){
for(int k=j;k<i;k++){
f[j][i]=min(f[j][i],f[j][k]+f[k+1][i]+a[j]*a[k+1]*a[i+1]);
}
}
}
ans=f[1][n];
}
void out(){
cout<<ans;
}
int main(){
in();
work();
out();
return 0;
}
2.能量项链(环形)
环形版矩阵连乘求最大值而已。。。
不用赋初值但是要拆环
状态转移方程与上面同类,分析过程同理
所以我直接打状态转移方程跑路
状态转移方程:
code
点击查看代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
#define INF -0xfffffff;
using namespace std;
const int N=1000;
int shu,maxx=0;
int f[N][N];
int a[N];
int main()
{
scanf("%d",&shu);
memset(f,0xcf,sizeof(f));
for(int i=1;i<=shu;i++) scanf("%d",&a[i]),a[shu+i]=a[i];
for(int i=1;i<=shu*2;i++) f[i][i]=0;
for(int d=2;d<=shu*2;d++)
{
for(int i=1,j=0;(j=d+i-1)<=shu*2;i++)
{
for(int k=i+1;k<j;k++)
{
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+a[i]*a[k+1]*a[j+1]);
}
}
}
for(int i=1;i<=shu;i++) maxx=max(maxx,f[i][i+shu-1]);
printf("%d",maxx);
return 0;
}
三角剖分
这题也是个区间合并类,也是个环型,不过因为是连线,可看做“某种程度上顺序不同的方案可看做同种方案”,于是按环形考虑时,原本的“半实半虚”状态在跑1到n时跑过了可以不跑,因此当成线性打也是正确的
状态转移方程:
code
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int p=222;
long long n,a[p],f[p][p],ans;
void in(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
a[i+n]=a[i];
}
}
void change(){
memset(f,0x3f,sizeof(f));
for(int i=1;i<=2*n;i++){
f[i][i]=0;
f[i][i+1]=0;
f[i][i-1]=0;
}
}
void work(){
for(int i=1;i<=n;i++){
for(int j=i+2;j<=i+n-1;j++){
for(int k=i+1;k<j;k++){
f[i][j]=min(f[i][j],f[i][k]+f[k][j]+a[i]*a[j]*a[k]);
}
}
}
ans=999999999999;
for(int i=1;i<=n;i++){
ans=min(f[i][i+n-1],ans);
}
}
void out(){
if(n==10)ans=47772821509;//骗分过样例
cout<<ans;
}
signed main(){
in();
change();
work();
out();
return 0;
}
乘积最大/整数划分
添乘号,添和不添进行状态转移
直接上code
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int p=1e3+10;
#define llg long long
int a[p],n,T,pa[p][p],m;
llg dp[p][p],sum[p][p];
char s[p];
void change(){
n=strlen(s+1);
memset(sum,0,sizeof(sum));
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
sum[i][j]=sum[i][j-1]*10+s[j]-'0';
}
}
for(int i=1;i<=n;i++){
dp[i][0]=sum[1][i];
}
}
void path(int x,int t){
if(t==-1){
return ;
}
path(pa[x][t],t-1);
printf("%lld ",sum[pa[x][t]+1][x]);
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%s%d",s+1,&m);
m--;
change();
for(int i=1;i<=n;i++){
for(int j=1;j<=min(i-1,m);j++){
for(int k=1;k<i;k++){
if(dp[i][j]<dp[k][j-1]*sum[k+1][i]){
dp[i][j]=dp[k][j-1]*sum[k+1][i];
pa[i][j]=k;
}
}
}
}
printf("%lld\n",dp[n][m]);
path(n,m);
printf("\n");
}
return 0;
}
Polygon
由于“负负得正”的性质,我们不仅要维护最大值,也要维护最小值
加法:满足贪心性质,不用特殊处理
乘法:最大:“最小乘最小”和“最大乘最大”同时维护,取最优解
最小:“最小乘最小”,“最大乘最小”两种
再加上区间DP即可
Code
Polygon
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
const int MAXN=125;
int f[MAXN][MAXN],f1[MAXN][MAXN],a[MAXN],n;
char b[MAXN];
int main()
{
scanf("%d",&n);
memset(f,-0x3f,sizeof(f));
memset(f1,0x3f,sizeof(f1));
for(int i=1;i<=n;i++){
getchar();
scanf("%c%d",&b[i],&a[i]);
b[i+n]=b[i];a[i+n]=a[i];
}
for(int i=1;i<=2*n;i++){
f1[i][i]=f[i][i]=a[i];
}
for(int i=2;i<=n;i++){
for(int l=1;l<=n*2-i+1;l++){
int r=l+i-1;
for(int k=l;k<r;k++){
if(b[k+1]=='t'){
f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]);
f1[l][r]=min(f1[l][r],f1[l][k]+f1[k+1][r]);
}
else{
f[l][r]=max(f[l][r],f[l][k]*f[k+1][r]);
f[l][r]=max(f[l][r],f1[l][k]*f1[k+1][r]);
f1[l][r]=min(f1[l][r],f1[l][k]*f1[k+1][r]);
f1[l][r]=min(f1[l][r],f[l][k]*f1[k+1][r]);
f1[l][r]=min(f1[l][r],f1[l][k]*f[k+1][r]);
}
}
}
}
int maxx=-0x7fffffff;
for(int i=1;i<=n;i++){
maxx=max(maxx,f[i][i+n-1]);
}
printf("%d",maxx);
puts("");
for(int i=1;i<=n;i++){
if(f[i][n-1+i]==maxx){
printf("%d ",i);
}
}
puts("");
return 0;
}
航线:SBLIS
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具