【算法竞赛刷题模板11】区间dp

23 篇文章 0 订阅

0.总结

  • Get to the points first. The article comes from LawsonAbs!
  • 区间dp是常见的一种dp题。本文给出一些求解区间dp常用的套路
  • 博客来源: LawsonAbs@CSDN

1.固定套路

区间dp的套路是固定的。

  • step1.确定区间长度len,一般是 len 属于[0,n]。按照从小到大的顺序遍历一次,这个作为dp问题处理的阶段。
  • step2.知道区间长度之后,接着确定区间的端点。因为区间长度已经确定好了,当知道了区间的左端点,区间的右端点就可以通过 j=i+len-1 计算出来
  • step3.这一步就是针对具体问题进行一个具体的分析,同时做一个状态的转移即可。
  • step4.再复杂一点儿的情况,可能给你的这个序列并不是一个线性的,而是一个环,那么就可能需要将这个环断成一个链,这个链是原来序列的2倍长。例如 1 2 3是一个环,那么我们就生成一个序列1 2 3 1 2 3。以此来解决存在环的问题。

2.练习题

2.1 【洛谷】P1880 石子合并

这是一道任何书中讲区间dp都会涉及到的模板题。可AC的代码如下:

#include<iostream>  
#include<cstdio>  
#include<cmath>  
using namespace std;   
int n,minl,maxl,f1[300][300],f2[300][300],num[300];  
int s[300];  
inline int d(int i,int j){return s[j]-s[i-1];}
  
//转移方程:f[i][j] = max(f[i][k]+f[k+1][j]+d[i][j];
int main()  
{   
    scanf("%d",&n);  
    for(int i=1;i<=n+n;i++)  //因为是一个环,所以需要开到两倍再枚举分界线,最后肯定是最大的 
    {  
        scanf("%d",&num[i]);  
        num[i+n]=num[i];  
        s[i]=s[i-1]+num[i];  
    }  
    for(int p=1;p<n;p++)  
    {  
        for(int i=1,j=i+p;(j<n+n) && (i<n+n);i++,j=i+p)  
        {  
            f2[i][j]=999999999;  
            for(int k=i;k<j;k++)  
            {  
                f1[i][j] = max(f1[i][j], f1[i][k]+f1[k+1][j]+d(i,j));   
                f2[i][j] = min(f2[i][j], f2[i][k]+f2[k+1][j]+d(i,j));  
            }  
        }  
    }  
    minl=999999999;  
    for(int i=1;i<=n;i++)  
    {  
        maxl=max(maxl,f1[i][i+n-1]);  
        minl=min(minl,f2[i][i+n-1]);  
    }  
    printf("%d\n%d",minl,maxl);  
    return 0;  
}

2.2 【洛谷】 P1063 能量项链

下面是我写的代码:

#include<iostream>
using namespace std;
const int N = 205;
//dp[i][j]表示左端点为i,长度为j时的最大值 
//arr[i][0]表示为头,arr[i][1]表示为尾 
//left[i][j]表示区间[i,j] 取最大值时的左端点值; right[i][j]表示区间[i,j]取最大值是右端点的值 
int dp[N][N],lef[N][N],righ[N][N],arr[N][2];
int n;

int main(){
	cin >> n;
	for(int i = 1;i<=n;i++){
		cin >> arr[i][0]; 		
		arr[i-1][1] = arr[i][0] ;//上个珠子的尾等于次头 		
	}
	arr[n][1] = arr[1][0] ;//重新计算第一颗珠子的尾 
	
	//预处理部分
	for(int i = 1;i<=n;i++){
	//	cout << arr[i][0]<<","<< arr[i][1]<<"\n";
		lef[i][i] = arr[i][0];
		righ[i][i] = arr[i][1];	
	}
	for(int i = 1;i<=n;i++){//断链成环 
		arr[n+i][0] = arr[i][0];
		arr[n+i][1] = arr[i][1];
		lef[n+i][n+i] = lef[i][i];
		righ[n+i][n+i] = righ[i][i];
	}
		
	//开始计算 
	for(int len = 2;len<=n;len++){//len的范围在[2,n]
		for(int i = 1;i<=2*n;i++){//区间的左端点 
			int y = i+len-1;//区间的右端点 
			for(int k = i;k<y && y<=2*n; k++){
				int temp = lef[i][k] * righ[i][k] * righ[k+1][y] + dp[i][k-i+1] + dp[k+1][y-k];
				if(dp[i][len] < temp){
					dp[i][len] = temp;
					lef[i][y] =  lef[i][k];
					righ[i][y] = righ[k+1][y]; 
				}
			}
		} 
	} 
	int res = 0;
	for(int i = 1;i<=n;i++){
		res = max(res,dp[i][n]);
	}
	cout << res<<"\n";	
}

虽然可AC,但是稍显冗余,其原因是,dp数组设的不好,导致需要用到lef,righ两个数组,很明显,这个是可以避免的。下面再给出一个精简版的代码:

#include <bits/stdc++.h>
using namespace std;
int f[405][405];
int n,a[205]; 
int main()
{
    cin >> n;
    for(int i=1;i<=n;i++)  //***对环形问题的处理技巧***
    {
        cin >> a[i];
        a[n+i]=a[i];
    } 
    for(int i=2;i<=n+1;i++)
    {
        for(int l=1;l+i-1<=2*n;l++)  //如果采取了上述策略,一定要将2*n个点都更新 
        {
            int r=l+i-1;
            for(int k=l+1;k<=l+i-2;k++)
                f[l][r]=max(f[l][r],f[l][k]+f[k][r]+a[l]*a[k]*a[r]); 
        }
    }
    int res=0;
    for (int i=1;i<=n;i++) res=max(res,f[i][n+i]);
    cout << res;
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

说文科技

看书人不妨赏个酒钱?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值