区间dp

Demo大约 7 分钟

状态定义

f[i][j]表示区间[i,j]的最优解

目标状态

f[1][n]:区间1~n的最优解

阶段的划分

区间长度。
因为我们需要求这个区间的最优解,那么我们要用他的子区间的最优解递推出来,那么他的子区间的长度是小于他的长度的,所有我们需要先将长度小的区间求出来。

框架

for(int len=2;len<=n;len++)//先枚举长度小的
{
	for(int l=1;l+len-1<=n;l++){//再按起点从左到右进行状态的计算
		int r=l+len-1;
		
	}
}

例题

石子合并

原题链接:https://www.acwing.com/problem/content/284/open in new window
题意:
有n堆石子,每堆石子有一个体积,我们需要把这n堆石子合并成一堆,并且每次合并的时候只能合并两个相邻堆的石子,合并两堆石子的代价是两堆石子的体积和,合并完之后新的堆的体积是这两堆的总体积。
求把这n堆合并成一堆所需要的最小代价。
思路:

f[i][j]表示区间[i,j]这段的石子合并的最小代价。
我们每次合并只能合并两堆,假设分界点是k,那么由[i,j]区间组成的这一堆是由[i,k]区间组成的这一堆和[k+1,j]区间组成的这一堆合并而成的。

那么合并的代价就是合并[i,k]这一堆的最小值加上合并[k+1,j]这一堆的最小值再加上[i,j]这个区间的石子体积的总和。

状态转移方程就是:f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1])

由于分界点k不确定,所以我们需要将k枚举。

初始化的时候由于求最小值,f数组设为最大,对于每一堆,即f[i][i],不需要合并,那么代价就是0.

#include<bits/stdc++.h>
using namespace std;
const int N=305;
int f[N][N];
int n,m;
int a[N],s[N];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		s[i]=s[i-1]+a[i];
	}
	memset(f,0x3f,sizeof f);
	for(int i=1;i<=n;i++) f[i][i]=0;
	for(int len=2;len<=n;len++){
		for(int l=1;l+len-1<=n;l++){
			int r=l+len-1;
			for(int k=l;k<r;k++){
				f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
			}
		}
	}
	cout<<f[1][n];
	return 0;
}

环形石子合并

原题链接:https://www.acwing.com/problem/content/1070/open in new window
题意:
有n堆石子围成一个圆摆放,将石子有序的合并成一堆。每堆石子有一个体积。 每次合并只能从相邻的两堆合并,合并的代价是两堆的体积和,合并之后形成的新堆的体积是原来两堆的总体积。
求合并需要的最小代价和最大代价。
思路:
围成一个环形的话,就可能会出现头部一些部分和尾部一些部分相连的情况,那么我们可以在这n堆后面再接上n堆,枚举每个起点,那么所有合并的情况都会被包括在内。
例如:1 2 3 4
将他接上4个变成:1 2 3 4 1 2 3 4 那么我们合并的话,4和1可以合并,4~2也可以合并,所有情况都被包含在内。
最后由于我们要求的是区间长度为n的所有区间的最值,因为不知道区间起点,那么我们就列举所有区间长度为n的所有状态取最值就可以了。

#include<bits/stdc++.h>
#include<vector>
#include<set>
using namespace std;
const int N=800;
int f[N][N];//最小值
int ff[N][N];//最大值
int a[N];
int s[N];
int n;
void sove(){
	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++) s[i]=s[i-1]+a[i];
	memset(f,0x3f,sizeof f);
	for(int i=1;i<=2*n;i++) f[i][i]=0;
	for(int len=2;len<=2*n;len++){
		for(int i=1;i+len-1<=2*n;i++){
			int j=i+len-1;
			for(int k=i;k<j;k++){
				f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
			}
		}
	}
	int ans=0x3f3f3f3f;
	for(int i=1;i<=n;i++){
		ans=min(ans,f[i][i+n-1]);
	}
	cout<<ans<<endl;
	for(int len=2;len<=2*n;len++){
		for(int i=1;i+len-1<=2*n;i++){
			int j=i+len-1;
			for(int k=i;k<j;k++){
				ff[i][j]=max(ff[i][j],ff[i][k]+ff[k+1][j]+s[j]-s[i-1]);
			}
		}
	}
	int ans1=0;
	for(int i=1;i<=n;i++){
		ans1=max(ans1,ff[i][i+n-1]);
	}
	cout<<ans1<<endl;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(),cout.tie();
	int t=1;
	//cin>>t;
	while(t--){
		sove();
	}
	return 0;
}

能量项链

原题链接:https://www.acwing.com/problem/content/322/open in new window
题意:
有条能量项链,上面有n个珠子。
一个珠子的头部有能量尾部也有能量
对于两颗相邻的珠子,前一个珠子的尾标记等于后一个珠子的头标记。
在合并的时候,只能相邻的两个珠子合并,假设前一个珠子的头部能量为m,尾部能量为n,那么后一个珠子的头部能量也为n,尾部能量是r,那么合并这两个珠子释放的能量就是m * n * r
求将这个项链合并成一个珠子所能释放的最大能量。

思路:
项链就是环形,先在后面接n个珠子。
然后对于区间[i,j],设分界点为k,那么这两段聚合的能量就是a[i] * a[k+1] * a[j]
最后再枚举每个长度为n的状态取最大值即可。

#include<bits/stdc++.h>
#include<vector>
#include<set>
using namespace std;
const int N=800;
int f[N][N];//最小值
int a[N];
int s[N];
int n;
void sove(){
	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++)s[i]=s[i-1]+a[i];
	for(int len=2;len<=2*n;len++){
		for(int i=1;i+len-1<=2*n;i++){
			int j=i+len-1;
			for(int k=i;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]);
			}
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		ans=max(ans,f[i][i+n-1]);
	}
	cout<<ans<<endl;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(),cout.tie();
	int t=1;
	//cin>>t;
	while(t--){
		sove();
	}
	return 0;
}

凸多边形的划分

原题链接:https://www.acwing.com/problem/content/1071/open in new window
题意:
有n个顶点的凸多边形,将顶点从1~n编号,每个点的权值是一个整数。
将这一个凸多边形划分成n-2个不想交的三角形,对于每个三角形,三个顶点的权值相乘都可以得到一个权值的乘积,求所有三角形的顶点权值乘积之和最少是多少。
思路:
f[i][j]表示顶点i到顶点j划分的三角形的方案中,值最小的方案。
因为三个点才能构成一个三角形,那么最小区间长度是3.
并且初始化的时候,因为一个点和两个点都构不成一个三角形,所以我们将区间长度为1和2的状态都设为0.

那么对于[i,j]这个区间,要把他划分成三角形,那么我们就列举i~j之间的分界点k,ijk划分成一个三角形,那么乘积就是a[i] * a[k] * a[j],又因为三角形不相交,那么我们再加上[i,k]这个区间划分三角形的最小值和[k,j]这个区间划分得到的最小值就可以了。

但是由于这题数据范围比较大,用个__int 128存吧

#include<bits/stdc++.h>
#include<vector>
#include<set>
using namespace std;
const int N=800;
inline __int128 read(){
    __int128 x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){
        if(ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9'){
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}
//输出函数
inline void print(__int128 x){
    if(x < 0){
        putchar('-');
        x = -x;
    }
    if(x > 9)
        print(x / 10);
    putchar(x % 10 + '0');
}
__int128 f[N][N];//最小值
__int128 a[N];
__int128 n;
void sove(){
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	memset(f,0x3f,sizeof f);
	for(int i=1;i<=n;i++){
		f[i][i+1]=0;
		f[i][i]=0;
	}
	for(int len=3;len<=n;len++){
		for(int i=1;i+len-1<=n;i++){
			int j=i+len-1;
			for(int k=i;k<j;k++){
				f[i][j]=min(f[i][j],f[i][k]+f[k][j]+a[i]*a[k]*a[j]);
			}
		}
	}
	print(f[1][n]);
}
int main(){
	int t=1;
	//cin>>t;
	while(t--){
		sove();
	}
	return 0;
}
Loading...