区间动态规划的归纳总结

个人理解

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

基本模式如下:

for(int i=1;i<=n;i++)f[i][i]=1;
for(int len=1;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:

例题

涂色问题:每次可以将连续的多个方块涂上某颜色,求涂成目标颜色的最少次数
解:
当s[i]==s[j],在涂i,j-1或i+1,j时,第一次多涂一格方块即可
当s[i]!=s[j],无法多涂来减少涂的次数,涂的总次数由两个区间直接相加而成
状态转移方程如下:
if(s[l]==s[r])f[l][r]=min(f[l][r-1],f[l+1][r])
else f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]),l<=k<r

即选点分离区间,分成 [l,k],[k+1,r]

#include<iostream>
#include<cmath>
#include<vector>
#include<cstring>
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];
	return 0;
} 

例题2:

例题2

字符串折叠问题:重复的多个字符串S(三个)可被折叠省略为3(S),求一个长字符串可被折叠成的最短折叠
解:
设k为断点,
在不考虑折叠时,f[l][r]=min(f[l][r],f[l][k]+f[k+1][r])
在考虑折叠时,让l,r以l,k折叠,则f[l][r]=min(f[l][r],f[l][k]+2+wei(len/(k-l+1)))

即选点作为折点,折叠区间[l,k]

#include<iostream>
#include<cstring>
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 wei(int x) {
    
    
	if(x==0)return 1;
	int t=0;
	while(x) {
    
    
		t++;
		x/=10;
	}
	return t;
}

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; l<=n; l++) {
    
    
			int r=l+len;
			if(r>=n)break;
			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+1)%x==0) {
    
    
					if(check(l,r,x))f[l][r]=min(f[l][r],wei((len+1)/x)+2+f[l][k]);
				}
			}
		}
	}
	cout<<f[0][n-1];
	return 0;
}

例题3:

例题

选取点作为根节点
则f[l][r]=max(f[l][k-1] x f[k+1][r]+f[k][k]),l<=k<=r

即选点分离区间,分成 [l,k],k,[k+1][r]

完整代码如下

#include<iostream>
#include<cstring>
#include<map>
using namespace std;

long long f[100][100],root[100][100],a[100];

void dfs(int l,int r){
    
    
	if(l>r)return;
	cout<<root[l][r]<<" ";
	if(l==r)return; 
	dfs(l,root[l][r]-1);
	dfs(root[l][r]+1,r);
}
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]=a[i],root[i][i]=i;
    
    for(int len=2;len<=n;len++){
    
    
    	for(int l=1,r=l+len-1;r<=n;l++,r++){
    
    
    		f[l][r]=-1e9;
    		for(int k=l+1;k<r;k++){
    
    
    			if(f[l][k-1]*f[k+1][r]+f[k][k]>f[l][r]){
    
    
    				f[l][r]=f[l][k-1]*f[k+1][r]+f[k][k];
    				root[l][r]=k;
				}
			} 
			if(f[l][l]+f[l+1][r]>f[l][r]){
    
    
				f[l][r]=f[l][l]+f[l+1][r];
				root[l][r]=l;
			}
			if(f[l][r-1]+f[r][r]>f[l][r]){
    
    
				f[l][r]=f[l][r-1]+f[r][r];
				root[l][r]=r;
			}
		}
	}
	cout<<f[1][n]<<endl;
	dfs(1,n);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43602607/article/details/114064972