动态规划之区间DP

定义

区间DP也叫合并类DP

合并 意思就是将两个或多个部分进行整合,当然也可以反过来,也就是是将一个问题进行分解成两个或多个部分

特点:

能将问题分解为两两合并的形式

一般思路:

对整个问题设最优值,枚举合并点,将问题分解成为左右两个部分,最后将左右两个部分的最优值进行合并得到原问题的最优值。

例题

合并石子

描述

传送门
在一个圆形操场的四周摆放着n堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
试设计一个算法。
选择一种合并石子的方案,使得做 n-1 次合并得分总和最大。
选择一种合并石子的方案,使得做 n−1 次合并得分总和最小。
对于给定n堆石子,计算合并成一堆的最小得分和最大得分。

思路

问题不具有局部最优代替整体最优,不能用贪心法

举个例子
N=6
贪心:                                         
第一次合并5 4 6 5 4 得分+5                  
第二次合并9 6 5 4   得分+9
第三次合并9 6 9     得分+9
第四次合并15 9      得分+15
第五次合并24        得分+24
共计62分

最优:
第一次合并7 6 5 4 2 得分+7
第二次合并13 5 4 2  得分+13
第三次合并13 5 6    得分+6
第四次合并13 11     得分+11
第五次合并24        得分+24
共计61分

但是可以拆分为多个子问题,再对其进行合并
设a[i][j]表示从第i堆到第j堆石子数总和
dp[i][j]表示将从第i堆石子合并到第j堆石子的最大得分
g[i][j]表示将从第i堆石子合并到第j堆石子的最小得分
由题意得
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+l][j]+a[i][j])
g[i][j]=min(g[i][j],g[i][k]+g[k+l][j]+a[i][j])
dp[i][i]=0 g[i][i]=0
因为我们是要求一个任意区间的和(a[i][j])因此我们可以用前缀和优化一下

code

code
#include<bits/stdc++.h>
using namespace std;
#define Elaina 0
const int N=2010
int sum,n,m,dp[N][N],g[N][N],a[N],s[N];
void DP(){
	for(int len=2;len<=n;len++){
		for(int i=1;i+len-1<=2*n;i++){
			int j=i+len-1;
			for(int k=i;k<j;k++){
				dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+s[j]-s[i-1]);
				g[i][j]=max(g[i][j],g[i][k]+g[k+1][j]+s[j]-s[i-1]);
			}
		}
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[n+i]=a[i];
	}
	for(int i=1; i<=n*2; i++){//前缀和预处理
		s[i]=s[i-1]+a[i];	
	}
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<=2*n;i++){
		dp[i][i]=0;
	}
	DP();
	int ans1=INF,ans2=0;
	for(int i=1;i<=n;i++){
		ans1=min(dp[i][n+i-1],ans1);
		ans2=max(g[i][n+i-1],ans2);
	}
	cout<<ans1<<endl<<ans2<<endl;
	return Elaina;
}
##整数划分 ###描述 如何把一个正整数N(N长度<20)划分为M(M>1)个部分,使这M个部分的乘积最大。N、M从键盘输入,输出最大值及一种划分方式。

思路

设dp[i][j]表示这个数前i位分成j段得到的最大乘积
易得
dp[i][j]=dp[k-1][j-1]*a[k][i]

想要输出划分方式只需记录决策点即可

code

code
#include<bits/stdc++.h>
using namespace std;
#define N 1010
#define ll long long
#define INF 0x3f3f3f3f
#define inf 0x3f
#define Elaina 0
ll ink,n,m,T,a[N][N],dp[N][N],path[N][N];
char ss[50];
void bag(){
	for(ll i=1; i<=n; i++){
		int ki=min(i,m);
		for(int j=1;j<=ki;j++){
			for(int k=1;k<=i;k++){
				if(dp[k-1][j-1]*a[k][i]>dp[i][j]){
					dp[i][j]=dp[k-1][j-1]*a[k][i];
					path[i][j]=k-1;
				}
			}
		}
	}
}
void backtrack(int n,int m){
	if(!m){
		return;
	}
	backtrack(path[n][m],m-1);
	for(int i=path[n][m]+1;i<=n;i++){
		cout<<ss[i];
	}
	cout<<" ";
	return ;
}
int main(){
	cin>>T;
	while(T--){
		memset(path,0,sizeof(path));
		memset(dp,0,sizeof(dp));
		memset(ss,0,sizeof(ss));
		memset(a,0,sizeof(a));
		cin>>ss+1;
		n=strlen(ss+1);
		cin>>m;
		for(int i=1;i<=n;i++){
			ink=0;
			for(int j=i;j<=n;j++){
				ink=ink*10+ss[j]-'0';
				a[i][j]=ink;
			}
		}
		dp[0][0]=1;
		bag();
		cout<<dp[n][m]<<endl;
		if(m==n){
			for(int i=1;i<=n;i++){
				cout<<ss[i]<<" ";
			}
		}else{
			backtrack(n,m);
		}
		cout<<endl;
	}
	return Elaina;
}

凸多边形的三角剖分

描述

给定一具有N个顶点(从1到N编号)的凸多边形,每个顶点的权均已知。问如何把这个凸多边形划分成N-2个互不相交的三角形,使得这些三角形顶点的权的乘积之和最小?

思路

按顺时针将顶点编号,设dp[i][j]表示i~j这一段连续顶点的多边形划分后的最小乘积(最优解),枚举点k,i,j相连成三角形,并把原多边形划分成两个子多边形,易得
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[j]*a[k])

code

code
#include<bits/stdc++.h>
using namespace std;
#define N 2010
#define ll long long
#define INF 0x3f3f3f3f
#define inf 0x3f
#define Elaina 0
ll dp[N][N],a[N],n,k;
void DP(){
	for(int len=2;len<n;len++){
		for(int i=1;i<=n-len;i++){
			int j=i+len;
			for(int k=i+1;k<j;k++){
				dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[k]*a[j]);
			}
		}
	}
}

int main(){
	cin>>n;
	memset(dp,inf,sizeof(dp));
	for(int i=1;i<=n;i++){
		cin>>a[i];
		dp[i][i+1]=0;	
	}
	DP();
	cout<<dp[1][n];
	return Elaina;
}
posted @ 2024-02-17 19:28  Elaina_0  阅读(14)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end