区间dp总结

持续更新…

1.P1005 矩阵取数
解题思路:
按行区间dp
  • f [ l ] [ r ] f[l][r] f[l][r]每行从 l l l r r r的符合条件的和,那么根据只能从左边或右边取数,有:
    f [ l ] [ r ] = m a x ( f [ l + 1 ] [ r ] + a [ l ] ∗ p [ k ] , f [ l ] [ r − 1 ] + a [ r ] ∗ p [ k ] ) f[l][r]=max(f[l+1][r]+a[l]*p[k],f[l][r-1]+a[r]*p[k]) f[l][r]=max(f[l+1][r]+a[l]p[k],f[l][r1]+a[r]p[k])
    这里的 k k k表示2的幂指数,联系到 k k k与已经取走的石子个数之间的关系,所以 k = m − ( r − l + 1 ) + 1 = m − ( r − l ) k=m-(r-l+1)+1=m-(r-l) k=m(rl+1)+1=m(rl),其中 m − ( r − l + 1 ) m-(r-l+1) m(rl+1)表示已经取走的石子的个数,由于最开始没有取走任何石子时, k k k为1,所以需要在取走的石子个数上加1。
  • 其次就是注意可能爆long long,需要用__int128来存储。输出__int128格式可采用如下:
void print(__int128 x){
	if(x>9) print(x/10);
	putchar('0'+x%10);
}
代码部分
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
__int128 map[85][85],f[85][85];
__int128 ans=0;
//按行区间dp
__int128 p[85];
int n,m;
__int128 dfs(int l,int r,__int128 a[]){
    if(l>r) return 0;
    if(f[l][r]) return f[l][r];
    f[l][r]=max(dfs(l+1,r,a)+a[l]*p[m-(r-l)],dfs(l,r-1,a)+a[r]*p[m-(r-l)]);
    return f[l][r];
} 
void print(__int128 x){
    if (x>9) print(x/10);
    putchar('0'+x%10);
}
int main(int argc, char** argv) {
    scanf("%d%d",&n,&m);
    __int128 ans=0, t=1;
    p[0]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&map[i][j]);
            if(i==1) p[j]=p[j-1]*2;
        }	
    }
    for(int i=1;i<=n;i++){
        memset(f,0,sizeof(f));//初始化 
        __int128 tmp=dfs(1,m,map[i]);
        ans+=tmp;
    }
    print(ans);
    return 0;
}
2.P1880 石子合并
解题思路:

最初拿到题目的时候,以为是用赫夫曼树的思想合并石子。但是这个合并的顺序是一定的,即只能和左边或者右边的石子合并,而不是从一堆石子中找出最小的两堆石子合并。在解题过程中需注意:

  • 石子排列成环形,如何对环形的石子进行每轮状态的更新?(利用将数组扩展成 2 n 2n 2n,将环形化成链状结构,方便下一步处理。注意保存的数组大小至少开到 2 n 2n 2n。)
  • 由于当前石子只能和左右的石子合并,化成链状之后就可以转化为区间dp问题,这里设 f [ i ] [ j ] f[i][j] f[i][j]表示从 i i i j j j个石子获得的总分,那么在合并石子的最大值 f [ i ] [ j ] f[i][j] f[i][j]时:
    f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ k ] + f [ k + 1 ] [ j ] + d ( i , j ) ) f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+d(i,j)) f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+d(i,j)),其中 d ( i , j ) d(i,j) d(i,j)为从 i i i j j j的前缀和,之所以加上 d ( i , j ) d(i,j) d(i,j)是因为最终合并的过程包括从 i i i j j j的前缀和。
  • 利用记忆化搜索减少复杂度。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#define mx 233233233
using namespace std;
int a[220],b[220];
//f[i][j]= max(f[i][j],f[i][k]+f[k][j]+d[i][j])
int f[220][220],f2[220][220];
int d(int i,int j){
	return b[j]-b[i-1];//i到j的前缀和
}
int dfs(int l,int r){
	if(l==r) return 0;
	if(f[l][r]!=0) return f[l][r];
	for(int k=l;k<r;k++){
		f[l][r]=max(f[l][r],dfs(l,k)+dfs(k+1,r)+d(l,r));
	}
	return f[l][r];
}

int dfs2(int i,int j){
	if(i==j) return 0;
	if(f2[i][j]!=mx)  return f2[i][j];
	for(int k=i;k<j;k++){
		f2[i][j]=min(f2[i][j],dfs2(i,k)+dfs2(k+1,j)+d(i,j));
	}
//	printf("%d\n",f2[i][j]);
	return f2[i][j];
}
int main(int argc, char** argv) {
	int N;
	int ans=-1,ans2=mx;
	scanf("%d",&N);
	for(int i=1;i<=N;i++){
		scanf("%d",&a[i]);
		a[i+N]=a[i];
	}
	for(int i=1;i<=2*N;i++){
		b[i]=b[i-1]+a[i];//化成链状 
		for(int j=1;j<=2*N;j++){
			f[i][j]=0;f2[i][j]=mx;
		}
	}
	dfs(1,2*N);dfs2(1,2*N);
	
	for(int i=1;i+N<=2*N;i++){
		ans2=min(ans2,f2[i][i+N-1]);
		ans=max(ans,f[i][i+N-1]);
	}
	
	printf("%d\n%d\n",ans2,ans);
	return 0;
}
3.P1063能量项链
解题思路:

P1880 石子合并有异曲同工之妙(因为都是区间dp问题),注意到合并时并非最开始想的从左到右顺序合并,在这里记 f [ l ] [ r ] f[l][r] f[l][r]为从 l l l r r r合并的总能量,那么有:
f [ l ] [ r ] = m a x ( f [ l ] [ r ] , f [ l ] [ k ] + f [ k + 1 ] [ r ] + a [ l ] ∗ a [ k + 1 ] ∗ a [ r + 1 ] ) f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]+a[l]*a[k+1]*a[r+1]) f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]+a[l]a[k+1]a[r+1])
其中 a [ l ] a[l] a[l]为左端点, a [ k + 1 ] a[k+1] a[k+1]为第一部分的右端点,也是第二部分的左端点, a [ r + 1 ] a[r+1] a[r+1]为第二部分的右端点。然后将环化成长度为 2 n 2n 2n的链即可。

#include <iostream>
#include<cstdio>
using namespace std;
#define ll long long
ll a[110+110];
ll f2[220][220];

int dfs(int l,int r){
	if(l>=r) return 0;//搜索终点,l==r
	if(f2[l][r])  return f2[l][r];//记忆化搜索 
	for(int k=l;k+1<=r;++k){
		f2[l][r]=max(f2[l][r],dfs(l,k)+dfs(k+1,r)+a[k+1]*a[l]*a[r+1]); 
	}
	return f2[l][r];
}

int main(int argc, char** argv) {
	int n;scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		a[i+n]=a[i]; 
	}
	dfs(1,2*n-1);//只能到2*n-1,为了保证递推公式里面的a[r+1]不超过a[2*n]
	ll ans=-1;
	for(int i=1;i<=n;i++){
		ans=max(ans,f2[i][i+n-1]);
	}
	printf("%lld",ans);
	return 0;
}
posted @ 2018-09-30 21:25  xzhws  阅读(53)  评论(0编辑  收藏  举报