蒟蒻の区间dp学习总结

17 篇文章 0 订阅

定义

区间dp其实就是一种建立在线性结构上的对区间的动态规划,dp本来就是很奇妙的东西,也没有什么套路,就是一种思考的数学思维方式,只有做足够多的题并且想的足够多才可能在比赛中做出来。

区间dp,顾名思义,在区间上dp,大多数题目的状态都是由区间(类似于dp[l][r]这种形式)构成的,就是我们可以把大区间转化成小区间来处理,然后对小区间处理后再回溯的求出大区间的值,主要的方法有两种,记忆化搜索和递推。

模板

无优化

memset(dp,0,sizeof(dp))//初始dp数组
for(int len=2;len<=n;len++){//枚举区间长度
    for(int i=1;i<n;++i){//枚举区间的起点
        int j=i+len-1;//根据起点和长度得出终点
        if(j>n) break;//符合条件的终点
        for(int k=i;k<=j;++k)//枚举最优分割点
            dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);//状态转移方程
    }
}

四边形优化

for(int len=2;len<=n;len++){
    for(int i = 1;i<=n;i++){
		int j = i+len-1;
		if(j>n) break;
		for(int k = s[i][j-1];k<=s[i+1][j];k++){
	    	if(dp[i][j]>dp[i][k]+dp[k+1][j]+w[i][j]){
				dp[i][j]=dp[i][k]+dp[k+1][j]+w[i][j];
				s[i][j]=k;
	    	}
		}
    }
}

例题

P2858 [USACO06FEB]Treats for the Cows G/S

思路

我们定义f[i][j]为卖掉i到j之间的临时得到的最大收益。

另外对与临时的价格我们做一个前缀和。

转移方程就应该是f[i][j]=max(f[i][j-1],f[i+1][j])+dis[j]-dis[i-1]f[i][j]=max(f[i][j−1],f[i+1][j])+dis[j]−dis[i−1]貌似跟楼下的不是很一样。

这是什么意思呢

既然是买点i到j之间的,那么这一天卖掉的一定是i或j,同时因为多了一天,所以我之前卖的应该滞后一天卖,也就是说每一个物品再增加一个单价,同时加上我现在卖出去的i或j,去一个较大值就可以了。

C o d e Code Code

#include<iostream>
using namespace std;
int dp[2010][2010];//dp数组
int a[2010],ans;//输入数组
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];//数据读入
	for(int i=1;i<=n;i++)
		for(int j=0;j<=i;j++){
			int l=i-j;//推出右边取了多少个
			dp[i][j]=max(dp[i-1][j]+a[n-l+1]*i,dp[i-1][j-1]+a[j]*i);//状态转移
            //n-l+1就是从右边数第l个在a数组中的下标
		}
	for(int i=1;i<=n;i++)
		ans=max(ans,dp[n][i]);//从最终状态中取一个最大值
	cout<<ans;
	return 0;
}

P1063 能量项链

思路

用f[l][r]表示以a[l]开头a[r]结尾的数串的最大和,如k为i,j之间任一节点,有f[l][r]=max(f[l][r],f[l][k]+f[k][r]+a[l]*a[k]*a[r]); 对l,r的定义自己一定要十分清晰,从而确定好循环的边界。

本题的小技巧:在环形问题中,可以选择(i+1)%n的方式,但也可以将n个元素复制一遍,变成2*n个元素,简化代码。

但也有问题随之而来,在更新时要将2*n个元素都更新,而不能只更新到前n个,否则访问到n+1~2n时会出错

C o d e Code Code

#include<bits/stdc++.h>
using namespace std;
int f[405][405];
int n,a[205];
int res;
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]); 
        }
    }
    for (int i=1;i<=n;i++) res=max(res,f[i][n+i]);
    cout<<res;
    return 0;
}

P3205 [HNOI2010]合唱队

思路

那么我们要怎么设计状态,我们想,每给人进入队伍里,只有2种可能,1种是从左边加入,另外1种是从右边进入,所以我们的装态是有3个数

f[i][j][0]表示的是第i人从左边进来的方案数

f[i][j][1]表示的是第j人从右边进来的方案数

从左边进来肯定前1个人比他高,前1个人有2种情况,要么在i+1号位置,要么在j号位置。

同理,从右边进来肯定前1个人比他矮,前1个人有2种情况,要么在j-1号位置,要么在i号位置。

C o d e Code Code

#include <bits/stdc++.h>
using namespace std;
const int mod=19650827;
int f[2010][2010][2],a[2010];
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][0]=1;
	for(int len=1;len<=n;len++)
		for(int i=1,j=i+len;j<=n;i++,j++){
			if(a[i]<a[i+1])f[i][j][0]+=f[i+1][j][0];
			if(a[i]<a[j])f[i][j][0]+=f[i+1][j][1];
			if(a[j]>a[i])f[i][j][1]+=f[i][j-1][0];
			if(a[j]>a[j-1])f[i][j][1]+=f[i][j-1][1];
			f[i][j][0]%=mod;
			f[i][j][1]%=mod;
		}
	cout<<(f[1][n][0]+f[1][n][1])%mod;
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值