区间动态规划的归纳总结

枚举区间断点,合并两区间

套路总结

  • 区间动态规划就是先计算出小区间的权值,大区间由两个已计算好的小区间合并而成
    我们通过枚举大区间被两个小区间拆分的断点来求出大区间的最优值。
    其很明显的至少由两层循环组成,且大部分都是三层循环
  • 通常习惯将:
  • 左右端点设为l,r,k设为端点,len设为区间长度
  • 通过枚举k为断点计算区间最优值
  • :通常将其倍长处理,然后求所有长度为 n 的区间即可

模板如下:

for(int i=1;i<=n;i++)f[i][i]=init();
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]=do(l,k,k+1,r);
	}
}

例题1:

  • 题目描述: 已知 n 石子的权值 ai,每次操作可将两堆相邻石子合并,并得到与合并后的石子数量一样的得分,求最小/最大得分。
  • 问题分析: 考虑 [l,r] 这堆石子是由 [l,k],[k+1,r] 这两堆石子合并而成
#include<bits/stdc++.h>
using namespace std;
int a[210],f[210][210],d[210][210];
int main() {
	int n;
	cin>>n;
	for(int i=1; i<=n; i++)cin>>a[i];
	for(int i=1; i<=n; i++)f[i][i]=d[i][i]=a[i];
	for(int len=1; len<=n; len++) {
		for(int l=1,r=l+len-1; r<=n; l++) {
			d[l][r]=1e9;
			f[l][r]=-1e9;
			for(int k=l; k<r; k++) {
				f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]+(a[r]-a[l-1]));
				d[l][r]=min(d[l][r],d[l][k]+d[k+1][r]+(a[r]-a[l-1]));
			}
		}
	}
	cout<<f[1][n]<<" "<<d[1][n];
}

例题2:

例题链接

  • 题目描述: 给定 n 个数 ai 在一个环上。给定 n 个符号 ci( × / + \times/+ ×/+)在每条边上,要求删除一条边,使得剩下的 n 个数的链通过任意的先后运算,可以变得最大的值。求这个最大的值。 n < = 50 , a i ∈ [ − 32768 , 32767 ] n<=50,a_i\in[-32768,32767] n<=50,ai[32768,32767]
  • 问题分析: 枚举断边再计算是显然的。但是计算有个明显的贪心错误,就是直接用 f [ l ] [ r ] f[l][r] f[l][r] 来表示区间 [ l , r ] [l,r] [l,r] 能算成的最大值,因为有负数和乘法的存在,无法通过这种局部最优推出全局最优了。不妨设置两个状态,区间 [ l , r ] [l,r] [l,r] 能算成的最大/最小值,这样貌似就 ok 了。环的话,倍长一下就 ok 了
  • 状态表示: f [ l ] [ r ] f[l][r] f[l][r]:区间 [ l , r ] [l,r] [l,r] 能算出的最大值; g [ l ] [ r ] g[l][r] g[l][r]:区间 [ l , r ] [l,r] [l,r] 能算出的最大值
  • 转移方程: 枚举断点 k ,然后分类判断整数负数之类就行了,转移方程略
#include<bits/stdc++.h>
using namespace std;
long long f[105][105],g[105][105],a[105],stk[105];
char c[105];
int main() {
	int n;
	cin>>n;
	for(int i=1; i<=n; i++) {
		cin>>c[i]>>a[i];
		c[n+i]=c[i];
		a[n+i]=a[i];
	}
	long long ans=-1e9,top=0;
	for(int i=1; i<=2*n; i++) {
		for(int j=1; j<=2*n; j++) {
			f[i][j]=-1e9;
			g[i][j]=1e9;
		}
	}
	for(int i=1; i<=2*n; i++)f[i][i]=g[i][i]=a[i];
	for(int len=2; len<=n; len++) {
		for(int l=1,r=l+len-1; r<=2*n; l++,r++) {
			for(int k=l; k<r; k++) {
				if(c[k+1]=='x') {
					f[l][r]=max(f[l][r],f[l][k]*f[k+1][r]);
					f[l][r]=max(f[l][r],f[l][k]*g[k+1][r]);
					f[l][r]=max(f[l][r],g[l][k]*f[k+1][r]);
					f[l][r]=max(f[l][r],g[l][k]*g[k+1][r]);
					g[l][r]=min(g[l][r],f[l][k]*f[k+1][r]);
					g[l][r]=min(g[l][r],f[l][k]*g[k+1][r]);
					g[l][r]=min(g[l][r],g[l][k]*f[k+1][r]);
					g[l][r]=min(g[l][r],g[l][k]*g[k+1][r]);
				} else {
					f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]);
					g[l][r]=min(g[l][r],g[l][k]+g[k+1][r]);
				}
			}
		}
	}
    for(int i=1;i<=n;i++)ans=max(ans,f[i][i+n-1]);
	cout<<ans<<endl;
	for(int i=1;i<=n;i++){
		if(f[i][i+n-1]==ans)cout<<i<<" ";
	}
	return 0;
}

例题3:

例题链接

  • 题目描述: 涂色问题:给定一排 n 个方块的初始颜色和目标颜色,一共有三种。每次可以将连续的多个方块涂上某颜色,求涂成目标颜色的最少次数。
  • 状态设置: f [ i ] [ j ] f[i][j] f[i][j]:将区间 [ i , j ] [i,j] [i,j] 涂成目标颜色需要的最少次数
  • 问题分析:
  • s [ i ] = = s [ j ] s[i]==s[j] s[i]==s[j] ,在涂 [ i , j − 1 ] [i,j-1] [i,j1] [ i + 1 , j ] [i+1,j] [i+1,j] 时,第一次多涂一格方块即可
  • s [ i ] ≠ s [ j ] s[i]\not=s[j] s[i]=s[j],无法多涂来减少涂的次数,涂的总次数由两个区间直接相加而成
  • 【解释】:为什么是直接相加?假如两个区间的中间部分不相同,则无法通过连涂得到,直接相加;假如两个区间的中间部分相同,例如: s [ k ] = s [ k + 1 ] s[k]=s[k+1] s[k]=s[k+1],那么在枚举断点的时候,完全可以自动去考虑 f [ l ] [ k + 1 ] + f [ k + 2 ] [ r ] f[l][k+1]+f[k+2][r] f[l][k+1]+f[k+2][r] f [ l ] [ k − 1 ] + f [ k ] [ r ] f[l][k-1]+f[k][r] f[l][k1]+f[k][r],因为条件1下, f [ l ] [ k + 1 ] < = f [ l ] [ k ] , f [ k ] [ r ] < = f [ k + 1 ] [ r ] f[l][k+1]<=f[l][k],f[k][r]<=f[k+1][r] f[l][k+1]<=f[l][k],f[k][r]<=f[k+1][r]
  • 转移方程:
  • s [ l ] = = s [ r ] : f [ l ] [ r ] = m i n ( f [ l ] [ r − 1 ] , f [ l + 1 ] [ r ] ) s[l]==s[r]:f[l][r]=min(f[l][r-1],f[l+1][r]) s[l]==s[r]f[l][r]=min(f[l][r1],f[l+1][r])
  • s [ l ] ≠ s [ r ] : f [ l ] [ r ] = m i n ( f [ l ] [ r ] , f [ l ] [ k ] + f [ k + 1 ] [ r ] ) , k ∈ [ l , r ) s[l]\not=s[r]: f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]),k\in[l,r) s[l]=s[r]f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]),k[l,r)
#include<bits/stdc++.h>
using namespace std;
char s[100];
int f[100][100];

int main(){
	cin>>s;
	int n=strlen(s);
	for(int i=0;i<n;i++)f[i][i]=1;
	for(int len=1;len<n;len++){
		for(int l=0,r=l+len-1;r<n;l++,r++){
			f[l][r]=1e9;
			if(s[l]==s[r])f[l][r]=min(f[l][r-1],f[l+1][r]);
			else{
				for(int k=l;k<r;k++)f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]);
			}
		}
	}
	cout<<f[0][n-1];
} 

例题4:

例题链接

  • 题目描述: 给定一个 1 × n 1\times n 1×n 的地图,在里面玩2048,每次可以合并相邻两个相同数字,并合并成原数字+1,问序列中出现的最大数字的值最大是多少。n<=248
  • 状态表示: f [ l ] [ r ] f[l][r] f[l][r]:区间 [l,r] 可以合并成一个什么数字,如果无法合成一个数,则其值为 -1
  • 转移方程: k ∈ [ l , r ) , i f ( f [ l ] [ k ] = = f [ k + 1 ] [ r ] ) f [ l ] [ r ] = m a x ( f [ l ] [ r ] , f [ l ] [ k ] + 1 ) k\in [l,r),if(f[l][k]==f[k+1][r])f[l][r]=max(f[l][r],f[l][k]+1) k[l,r)if(f[l][k]==f[k+1][r])f[l][r]=max(f[l][r],f[l][k]+1)
  • 初始值: f [ i ] [ i ] = a [ i ] f[i][i]=a[i] f[i][i]=a[i]
  • 时间复杂度: O(n3)
#include<bits/stdc++.h>
using namespace std;
int f[1000][1000],ans=-1e9;
int main() {
	int n;
	cin>>n;
	for(int i=1; i<=n; i++){
		cin>>f[i][i];
	    ans=max(ans,f[i][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++){
				if(f[l][k]==f[k+1][r])f[l][r]=max(f[l][r],f[l][k]+1);
			}
			ans=max(ans,f[l][r]);
		}
	}
	cout<<ans;
	return 0;
}

字符串折叠问题,枚举折点

例题1:

例题链接

  • 题目描述: 字符串折叠问题:重复的多个字符串S(三个)可被折叠省略为3(S),求一个长字符串可被折叠成的最短折叠长度。例如: A A A A A A A A A A B A B A B C C D = 9 ( A ) 3 ( A B ) C C D = 12 AAAAAAAAAABABABCCD = 9(A)3(AB)CCD=12 AAAAAAAAAABABABCCD=9(A)3(AB)CCD=12
  • 状态表示: f [ l ] [ r ] f[l][r] f[l][r] 表示区间 [ l , r ] [l,r] [l,r] 内的字符串可被折叠成的最短长度
  • 转移方程:
  • 不考虑折叠: 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])
  • 考虑折叠:让 [ l , r ] [l,r] [l,r] [ l , k ] [l,k] [l,k] 折叠,若可以折叠,则 f [ l ] [ r ] = m i n ( f [ l ] [ r ] , f [ l ] [ k ] + 2 + l e n / ( k − l + 1 ) ) f[l][r]=min(f[l][r],f[l][k]+2+len/(k-l+1)) f[l][r]=min(f[l][r],f[l][k]+2+len/(kl+1))
#include<bits/stdc++.h>
using namespace std;

char s[150];
int f[150][150];

int check(int l,int r,int x) {
	for(int i=l; i<=r; i++) {
		if(s[i]!=s[(i-l)%x+l])return 0;
	}
	return 1;
}
int main() {
	cin>>s;
	int n=strlen(s);

	for(int i=0; i<n; i++)f[i][i]=1;
	for(int len=2; len<=n; len++) {
		for(int l=0,r=len-1; r<n; l++,r++) {
			f[l][r]=1e9;
			for(int k=l; k<r; k++)f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]);
			for(int k=l; k<r; k++) {
				int x=k-l+1;
				if(len%x==0) {
					if(check(l,r,x))f[l][r]=min(f[l][r],int(log10(len/x))+1+2+f[l][k]);
				}
			}
		}
	}
	cout<<f[0][n-1];
}

例题2:

例题链接

  • 题目描述: 字符串折叠问题:重复的多个字符串 S(三个及以上) 可被折叠省略为 3(S),求一个长字符串可被折叠成的最短折叠的折叠形式,若有多种方案,输出字典序最大的方案。例如: A A A A A A A A A A B A B A B C C D = 9 ( A ) 3 ( A B ) C C D AAAAAAAAAABABABCCD = 9(A)3(AB)CCD AAAAAAAAAABABABCCD=9(A)3(AB)CCD
  • 状态表示:
  • f [ i ] [ j ] f[i][j] f[i][j]:区间 [ l , r ] [l,r] [l,r] 能压缩成的最短字符串的长度
  • g [ i ] [ j ] g[i][j] g[i][j]:区间 [ l , r ] [l,r] [l,r] 能压缩成的最短字符串
  • 初始状态: f [ l ] [ r ] = l e n , g [ l ] [ r ] = s [ l ] [ r ] f[l][r]=len,g[l][r]=s[l][r] f[l][r]=len,g[l][r]=s[l][r]
  • 转移方程:
  • 不考虑折叠:
  • 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])
  • g [ l ] [ r ] = f [ l ] [ r ] > f [ l ] [ k ] + f [ k + 1 ] [ r ] ? g [ l ] [ k ] + g [ k + 1 ] [ r ] : g [ l ] [ r ] g[l][r]=f[l][r]>f[l][k]+f[k+1][r]?g[l][k]+g[k+1][r]:g[l][r] g[l][r]=f[l][r]>f[l][k]+f[k+1][r]?g[l][k]+g[k+1][r]:g[l][r]
  • 考虑折叠, [ l , r ] [l,r] [l,r] [ l , k ] [l,k] [l,k] 折叠,若可折叠
  • g [ l ] [ r ] = f [ l ] [ r ] > f [ l ] [ k ] + 2 + l e n / ( k − l + 1 ) ? 新 字 符 串 : g [ l ] [ r ] g[l][r]=f[l][r]>f[l][k]+2+len/(k-l+1)?新字符串:g[l][r] g[l][r]=f[l][r]>f[l][k]+2+len/(kl+1)?g[l][r]
  • f [ l ] [ r ] = f [ l ] [ r ] > f [ l ] [ k ] + 2 + l e n / ( k − l + 1 ) ? f [ l ] k ] + 2 + l e n / ( k − l + 1 ) : f ] l ] [ r ] f[l][r]=f[l][r]>f[l][k]+2+len/(k-l+1)?f[l]k]+2+len/(k-l+1):f]l][r] f[l][r]=f[l][r]>f[l][k]+2+len/(kl+1)?f[l]k]+2+len/(kl+1)f]l][r]

例题3:

例题链接

  • 题目描述: 字符串折叠问题:给定一个小写字符串,设 x 为一个子串,n 个连续的 x 可以折叠成 Mx+logn个R(并且logn为整数)。
  • 问题分析:
  • 由于有字符串最开头默认M的要求,因此正常 dp 状态不够用,我们默认直接在字符串最开头添加一个M 。
  • f [ l ] [ r ] [ 0 ] f[l][r][0] f[l][r][0] [ l , r ] [l,r] [l,r] 内压缩后的字符串第一个字母必须是M,且不可有额外的M
  • f [ l ] [ r ] [ 1 ] f[l][r][1] f[l][r][1] [ l , r ] [l,r] [l,r] 内压缩后的字符串第一个字母必须是M,且有多个M
  • 不考虑折叠: f [ l ] [ r ] [ 0 ] = m i n ( f [ l ] [ r ] [ 0 ] , m i n ( f [ l ] [ k ] [ 0 ] , f [ l ] [ k ] [ 1 ] ) , m i n ( f [ k + 1 ] [ r ] [ 0 ] , f [ k + 1 ] [ r ] [ 1 ] ) ) f[l][r][0] = min(f[l][r][0],min(f[l][k][0],f[l][k][1]),min(f[k+1][r][0],f[k+1][r][1])) f[l][r][0]=min(f[l][r][0],min(f[l][k][0],f[l][k][1]),min(f[k+1][r][0],f[k+1][r][1]))
  • 考虑折叠:让 [ l , r ] [l,r] [l,r] [ l , k ] [l,k] [l,k] 折叠,则 f [ l ] [ r ] = m i n ( f [ l ] [ r ] , s o l v e ( l , k , r ) ) f[l][r] = min(f[l][r],solve(l,k,r)) f[l][r]=min(f[l][r],solve(l,k,r))
#include<bits/stdc++.h>
using namespace std;
int f[105][105];
string p,g[105][105],s[105][105],temp="";

int check(int l,int r,int x){
	for(int i=l;i<=r;i++){
		if(p[(i-l)%x+l]!=p[i])return 0;
	}
	return 1;
}
int main(){
	cin>>p;
	for(int i=0;i<p.size();i++){
		f[i][i]=1;
		g[i][i]=p[i];
	}
	for(int i=0;i<p.size();i++){
		for(int j=i;j<p.size();j++){
			if(i==j)s[i][j]=p[j];
			else s[i][j]=s[i][j-1]+p[j];
		}
	}
	for(int len=2;len<=p.size();len++){
		for(int l=0,r=l+len-1;r<p.size();l++,r++){
			f[l][r]=len;
			g[l][r]=s[l][r];
			
			for(int k=l;k<r;k++){
				if(f[l][r]>f[l][k]+f[k+1][r]){
					f[l][r]=f[l][k]+f[k+1][r];
					g[l][r]=g[l][k]+g[k+1][r];
				}
			}
			for(int k=l;k<r;k++){
				int x=k-l+1;
				if(len%x==0) {
					if(check(l,r,x)){
						if(f[l][r]>int(log10(len/x))+1+2+f[l][k]){
							g[l][r]=temp+to_string(len/x)+'('+g[l][k]+')';
							f[l][r]=int(log10(len/x))+1+2+f[l][k];
						}
					}
				}
			}
		}
	}
	cout<<g[0][p.size()-1];
}

只能从左边界或右边界添加元素的区间DP

套路

  • 区间 [ l , r ] [l,r] [l,r] 并不能被拆分为 [ l , k ] , [ k + 1 ] [ r ] [l,k],[k+1][r] [l,k],[k+1][r],只能被拆分为 [ l − 1 , r ] , f [ l , r − 1 ] [l-1,r],f[l,r-1] [l1,r],f[l,r1],因为最后加入一个点只能从最左边或最右边加入。

例题1:从左边界或右边界添加元素(模板题)

例题链接

  • 题目描述: 假设有 n 个人,每个人身高为 hi,排成一列。从前往后依次重新进入新的队列中。如果当前加入的人比前一个人高,则其只能加入到新的队列的最后面;否则其只能加入到新的队列的最前面。给定一个最终的排列,请问有多少种原来的排列满足重新排队之后变成最终的样子。n<=1000
  • 状态表示: f [ l ] [ r ] [ 0 / 1 ] f[l][r][0/1] f[l][r][0/1]:最终排列为 [l,r] 且最后加入的人是最前面/最后面的人的方案数。
  • 转移方程:
  • i f ( a [ l ] < a [ l + 1 ] ) f [ l ] [ r ] [ 0 ] + = f [ l + 1 ] [ r ] [ 0 ] if(a[l]<a[l+1])f[l][r][0]+=f[l+1][r][0] if(a[l]<a[l+1])f[l][r][0]+=f[l+1][r][0]
  • i f ( a [ l ] < a [ r ] ) f [ l ] [ r ] [ 0 ] + = f [ l + 1 ] [ r ] [ 1 ] if(a[l]<a[r])f[l][r][0]+=f[l+1][r][1] if(a[l]<a[r])f[l][r][0]+=f[l+1][r][1]
  • i f ( a [ r ] > a [ r − 1 ] ) f [ l ] [ r ] [ 1 ] + = f [ l ] [ r − 1 ] [ 1 ] if(a[r]>a[r-1])f[l][r][1]+=f[l][r-1][1] if(a[r]>a[r1])f[l][r][1]+=f[l][r1][1]
  • i f ( a [ r ] > a [ l ] ) f [ l ] [ r ] [ 1 ] + = f [ l ] [ r − 1 ] [ 0 ] if(a[r]>a[l])f[l][r][1]+=f[l][r-1][0] if(a[r]>a[l])f[l][r][1]+=f[l][r1][0]
  • 目标值: f [ 1 ] [ n ] [ 0 ] + f [ 1 ] [ n ] [ 1 ] f[1][n][0]+f[1][n][1] f[1][n][0]+f[1][n][1]
  • 初始值: 按理解是 f [ i ] [ i ] [ 0 ] = f [ i ] [ i ] [ 1 ] = 1 f[i][i][0]=f[i][i][1]=1 f[i][i][0]=f[i][i][1]=1,可是如果只有一个人,则方案数为2,错了。我们默认一个人的时候,是从左边进去的。即: f [ i ] [ i ] [ 0 ] = 1 f[i][i][0]=1 f[i][i][0]=1
#include<bits/stdc++.h>
using namespace std;
long long f[1005][1005][2],a[1005],mod=19650827;
int main() {
	int n;
	cin>>n;
	for(int i=1; i<=n; i++) {
		cin>>a[i];
		f[i][i][0]=1;
	}
	for(int len=2; len<=n; len++) {
		for(int l=1,r=l+len-1; r<=n; l++,r++) {
			if(l+1<=n&&a[l]<a[l+1])f[l][r][0]+=f[l+1][r][0];
			if(a[l]<a[r])f[l][r][0]+=f[l+1][r][1];
			if(r-1>=1&&a[r]>a[r-1])f[l][r][1]+=f[l][r-1][1];
			if(a[r]>a[l])f[l][r][1]+=f[l][r-1][0];
			f[l][r][0]%=mod;
			f[l][r][1]%=mod;
		}
	}
	cout<<(f[1][n][0]+f[1][n][1])%mod;
	return 0;
}

例题2:带点思维的区间dp

例题链接

  • 题目描述: 给定 n 个数 a i a_i ai,尝试为这 n 个数排列顺序,使得排完顺序之后, ∑ i = 1 n ( m a x ( a 1 , a 2 , … … , a i ) − m i n ( a 1 , a 2 , … … , a i ) ) \sum_{i=1}^n (max(a_1,a_2,……,a_i)-min(a_1,a_2,……,a_i)) i=1n(max(a1,a2,,ai)min(a1,a2,,ai)) 最小。 n < = 2000 n<=2000 n<=2000
  • 问题分析: 假设 b 1 , b 2 , … … , b t b_1,b_2,……,b_t b1,b2,,bt 是新序列的前 t t t 个。
  • m a x ( b 1 , b 2 , … … , b t ) < x < y max(b_1,b_2,……,b_t)<x<y max(b1,b2,,bt)<x<y,则不可能出现 x x x y y y 之后选的情况。
  • m i n ( b 1 , b 2 , … … , b t ) > x > y min(b_1,b_2,……,b_t)>x>y min(b1,b2,,bt)>x>y,则不可能出现 x x x y y y 之后选的情况。
  • 因此选的数一定是大于等于 m a x ( b 1 , b 2 , … … , b t ) max(b_1,b_2,……,b_t) max(b1,b2,,bt) 的第一个数或小于等于 m i n ( b 1 , b 2 , … … , b t ) min(b_1,b_2,……,b_t) min(b1,b2,,bt) 的第一个数。
  • 排个序之后, ( b 1 , b 2 , … … , b t ) (b_1,b_2,……,b_t) (b1,b2,,bt) 就变成了连续的 [ l , r ] [l,r] [l,r] 。那么就相当于每次操作要么从右边添加一个数字,要么从左边添加一个数字
  • 状态设置: f [ i ] [ j ] f[i][j] f[i][j]:表示第 l 个数到第 r 个数字的 ∑ i = l r ( m a x ( a l , … … , a i ) − m i n ( a 1 , … … , a i ) ) \sum_{i=l}^r (max(a_l,……,a_i)-min(a_1,……,a_i)) i=lr(max(al,,ai)min(a1,,ai)) 的最小值。
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;

long long f[N][N],a[N];
int main() {
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++)f[i][i]=0;
	//for(int i=1;i<=n;i++)pre[i]=pre[i-1]+a[i]; 
	for(int len=2;len<=n;len++){
		for(int l=1,r=l+len-1;r<=n;l++,r++){
			f[l][r]=min(f[l+1][r]+a[r]-a[l],f[l][r-1]+a[r]-a[l]);
		}
	}
	cout<<f[1][n];
	return 0;
}
//f[l][r]:[l,r]=[l+1,r]+a[r]-a[l]  

类似倍增的合并区间问题

例题1

  • 题目描述: 给定一个 1 × n 1\times n 1×n 的地图,在里面玩2048,每次可以合并相邻两个相同数字,并合并成原数字+1,问序列中出现的最大数字的值最大是多少。n<=2e5,ai<=40
  • 问题分析: 与前一道题类似,但是 n 变大了,ai变小了。我们看看能不能靠着 ai 小的特点设置状态,来加速以下 dp 。原理:如果区间 [l,r] 能合成 i,其必然是两个区间能合成 i-1 的区间合成的。
  • 状态表示: f [ i ] [ j ] f[i][j] f[i][j]:左端点为 j,能合并出 i 这个数字的右端点的位置
  • 转移方程: f [ i ] [ j ] = f [ i − 1 ] [ f [ i − 1 ] [ j ] + 1 ] f[i][j]=f[i-1][f[i-1][j]+1] f[i][j]=f[i1][f[i1][j]+1],其中 i 最大不超过 60
  • 初始值: f [ a [ i ] ] [ i ] = i f[a[i]][i]=i f[a[i]][i]=i
#include<bits/stdc++.h>
using namespace std;
int f[61][262150],ans=-1e9;
int main() {
	int n,x;
	cin>>n;
	for(int i=1; i<=n; i++){
		cin>>x;
		f[x][i]=i;
		ans=max(ans,x);
	}
	for(int i=2;i<=60;i++){
		for(int j=1;j<=n;j++){
			if(f[i-1][j]!=0)f[i][j]=f[i-1][f[i-1][j]+1];
			if(f[i][j]!=0)ans=max(ans,i);
		}
	}
	cout<<ans;
	return 0;
}

二维区间dp

思路

  • 原理: 指的是在矩阵中使用区间 dp ,思路与线性的区间 dp 相似,都是大区间由两个小区间合并而来。
  • 状态表示: f [ x 1 ] [ y 1 ] [ x 2 ] [ y 2 ] f[x1][y1][x2][y2] f[x1][y1][x2][y2]:左上角是 ( x 1 , y 1 ) (x1,y1) (x1,y1) ,右下角是 ( x 2 , y 2 ) (x2,y2) (x2,y2) 的矩阵的最优值
  • 转移方程:
  • f [ x 1 ] [ y 1 ] [ x 2 ] [ y 2 ] = m a x ( f [ x 1 ] [ y 1 ] [ x 2 ] [ y 2 ] , f [ x 1 ] [ y 1 ] [ k ] [ y 2 ] + f [ k + 1 ] [ y 1 ] [ x 2 ] [ y 2 ] f[x1][y1][x2][y2]=max(f[x1][y1][x2][y2],f[x1][y1][k][y2]+f[k+1][y1][x2][y2] f[x1][y1][x2][y2]=max(f[x1][y1][x2][y2],f[x1][y1][k][y2]+f[k+1][y1][x2][y2]
  • f [ x 1 ] [ y 1 ] [ x 2 ] [ y 2 ] = m a x ( f [ x 1 ] [ y 1 ] [ x 2 ] [ y 2 ] , f [ x 1 ] [ y 1 ] [ x 2 ] [ k ] + f [ x 1 ] [ k + 1 ] [ x 2 ] [ y 2 ] f[x1][y1][x2][y2]=max(f[x1][y1][x2][y2],f[x1][y1][x2][k]+f[x1][k+1][x2][y2] f[x1][y1][x2][y2]=max(f[x1][y1][x2][y2],f[x1][y1][x2][k]+f[x1][k+1][x2][y2]
在这里插入代码片

例题1:棋盘分隔

例题链接

  • 题目描述: 一个 8 × 8 8\times 8 8×8 的棋盘,每个位置都有对应的得分 a i j a_{ij} aij,使用 n-1 刀将其分割成 n 块矩阵,并且每一刀都需要沿边切掉。每一个矩形的分数为这个矩形中每个位置得分的和。求如何切使得方差最小,输出最小方差。 0 < = w i j < 100 , 2 < = n < = 14 0<=w_{ij}<100,2<=n<=14 0<=wij<100,2<=n<=14
  • 问题分析: 平均值 x = ∑ w i j n x = \frac{\sum w_{ij}}{n} x=nwij,方差 σ = ∑ ( x i − x ) 2 n \sigma=\frac{\sum (x_i-x)^2}{n} σ=n(xix)2。我们只需要让 ( x i − x ) 2 n \frac{(x_i-x)^2}{n} n(xix)2 的和尽可能小即可。
  • 状态表示: f [ i ] [ j ] [ x ] [ y ] [ z ] : f[i][j][x][y][z]: f[i][j][x][y][z] 左上角 ( i , j ) (i,j) (i,j) 右下角 ( x , y ) (x,y) (x,y) 的矩阵被割成 z 块的最小 ( x i − x ) 2 n \frac{(x_i-x)^2}{n} n(xix)2 和。
  • 转移方程: 枚举左右切割点,上下切割点转移即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值