zoukankan      html  css  js  c++  java
  • 蒟蒻の区间dp学习总结

    定义

    区间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;
    }
    
    她透过我的血,看到了另一抹殷红
  • 相关阅读:
    IE9的兼容性
    element 弹框关闭报错
    时间选择器moment格式化存在时差问题
    项目常见bug
    函数封装——函数封装——函数封装
    element-ui 日期选择器范围时间限制
    vue + element 创建教程
    Html基础学习
    HTML、PHP、CSS、JS之间的关系
    vs连接MySQL
  • 原文地址:https://www.cnblogs.com/zhangbeini/p/13771243.html
Copyright © 2011-2022 走看看