• 动态规划


    简单动态规划


    前置知识

    • 英文缩写
    • e.g. 例如
    • etc. 等等
    • P.S. 备注

    硬币问题

    您有无限多的硬币,面值为1,5,10,20,50,100
    给定一个数额w,问您最少用多少枚硬币可以凑出w

    • 贪心
      • 先尽量用100的,然后尽量用50的……以此类推.
      • e.g. 666 = 6100+150+110+15+1*1,共用10枚硬币.
      • 贪心是一种只考虑眼前情况的策略.
      • 尽管这一套硬币面值可以采用贪心策略,但是迟早要栽跟头的.
        • 新的硬币面值:1,5,11.
        • 如果我们要凑出15,贪心策略是:15 = 11+4*1,共用5枚硬币.
        • 而最佳策略是:15 = 3*5,共用3枚硬币.
    • 性质
      • w=15时,我们取了11,接下来面对w=4的情况
      • w=15时,如果我们取5,接下来就面对w=10的情况
      • 我们记“凑出n需要用到的最少硬币数量”为f[n].
      • w=15时,选cost最低那个
        • 11:cost=f[4]+1=4+1=5
        • 5:cost=f[10]+1=2+1=3
        • 1:cost=f[14]+1=4+1=5
      • 性质:f[n]只于f[n-1],f[n-5],f[n-11]相关
      • 更确切的说:f[n]=min{f[n-1],f[n-5],f[n-11]}+1
    • 代码
    #include <bits/stdc++.h>
    
    using namespace std;
    
    int w;
    int f[1005];
    
    int main(){
    	cin >> n;
    	/******************************************/
    	//push(递推)型结构	
    	for(int i = 1; i <= n; ++i) f[i] = 0x7fffffff;
    	for(int i = 0; i <= n; ++i){
    		f[i + 1] = min(f[i + 1], f[i] + 1);
    		f[i + 5] = min(f[i + 5], f[i] + 1);
    		f[i + 11] = min(f[i + 11], f[i] + 1);
    	}
    	//pull(递归)型结构
    	for(int i = 1, cost; i <= n; ++i){
    		cost = 0x7fffffff;
    		if(i >= 1) cost = min(cost, f[i - 1] + 1);
    		if(i >= 5) cost = min(cost, f[i - 5] + 1);
    		if(i >= 11) cost = min(cost, f[i - 11] + 1);
    		f[i] = cost;
    	}
    	/******************************************/
    	cout << f[n];
    	return 0;
    }
    

    硬币问题原理

    • f[n]只与f[n-1],f[n-5],f[n-11]相关

    • 我们只关心f[w]的值,不关心是怎么取到w的

    • 可见,此做法比暴力快,因为我们舍弃了冗余的信息;我们只记录了对解决问题有帮助的信息-f[n]

    • 我们能这样干,取决于问题的性质:求出f[n],只需要知道几个更小的f[c].

    • 我们将求解f[c]称作求解f[n]的“子问题”.

    • 这就是DP(动态规划,dynamic programming).

    • 将一个问题拆成几个子问题,分别求解这些子问题,即可推断出大问题的解.


    DP(动态规划,dynamic programming)

    无后效性

    一旦f[n]确定,“我们如何凑出f[n]”就再也用不着了。
    要求出f[15],只需要知道f[14],f[10],f[4]的值,而f[14],f[10],f[4]是如何算出来的,对之后的问题没有影响。
    “未来与过去无关”,这就是无后效性。

    (严格定义:如果给定某一阶段的状态,则在这一阶段以后过程的发展不受这阶段以前各段状态的影响。)

    最优子结构

    回顾我们对f[n]的定义:
    我们记“凑出n需要用到的最少硬币数量”为f[n].

    f[n]的定义就已经蕴含了“最优”。
    利用w=14,10,4的最优解,我们即可算出w=15的最优解。

    大问题的最优解可以由小问题的最优解推出,这个性质叫做“最优子结构性质”。

    DP条件

    • 大问题能拆成小问题
    • 满足无后效性
    • 满足最优子结构性质

    DAG最短路

    给定一个城市的地图,所有的道路都是单行道,而且不会构成环。每条道路都有过路费,问您从S点到T点花费的最少费用。

    • 记从S到P的最少费用为f[p]
    • f[T] = min(f[C] + 20, f[D] + 10);

    代码

    
    

    数字三角形

    数字三角形

    代码

    #include<bits/stdc++.h>
    
    using namespace std;
    
    int n;
    
    int a[1001][1001+10],dp[1001][1001+10];
    
    int main(){
    	
    	cin>>n;
    	
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=i;++j){
    			cin>>a[i][j];
    		}
    	}
    	
    	for(int i=1;i<=n+1;++i) dp[n][i]=a[n][i];
    	
    	for(int i=n-1;i>=1;--i){
    		for(int j=1;j<=i;++j){
    			/*if(i+1==n/2&&(j==n/2||j+1==n/2)){
    				dp[i][j]=a[i][j]+dp[n/2][n/2];
    				continue;
    			}*/
    			dp[i][j]=max(a[i][j]+dp[i+1][j],a[i][j]+dp[i+1][j+1]);
    		}
    	}
    	
    	cout<<dp[1][1];
    	
    	return 0;
    }
    

    最大字段和

    最大子段和
    给出一个整数序列,问最大字段和。

    • e.g. [2,-1,3,-5,3]的最大字段和是2-1+3=4.
    • 要求一个O(n)的算法

    代码

    #include<bits/stdc++.h>
    
    using namespace std;
    
    const int N=200001;
    
    int n,ans;
    int a[N];
    int S[N],A[N],dp[N];;//S前缀和 , A最小前缀和 
    
    void doxx_1(){
        for(int i=1; i<=n; ++i){
    		dp[i]=max(dp[i-1]+a[i],a[i]);
    		ans=max(ans,dp[i]);
    	}
    }
    
    void doxx_2(){
    	int sum=0;
        for(int i=1;i<=n;++i){
            sum>=0?sum+=a[i]:sum=a[i];
            ans=max(ans,sum);
        }
    }
    
    void doxx_3(){
        for(int i=1;i<=n;++i) S[i]=S[i-1]+a[i];
        //for(int i=1;i<=n;++i) A[i]=min(A[i-1],S[i-1]);
        for(int i=1;i<=n;++i){
            A[i]=min(A[i-1],S[i-1]);
            ans=max(ans,S[i]-A[i]);
        }
    }
    
    int main(){ 
        cin>>n;
        for(int i=1;i<=n;++i) cin>>a[i];
        ans=a[1];
        doxx_1();
        //doxx_2();
        //doxx_3();
        cout<<ans;
        return 0;
    }
    

    最长上升子序列

    给定长度为n的序列a,从a中抽取出一个子序列,这个子序列需要单调递增。问最长的上升子序列(LIS)的长度。

    • e.g. 1,5,3,4,6,9,7,8
    • LIS为1,3,4,6,7,8 长度为6

    O(n^2)代码

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e5 + 1;
    
    int n,ans;
    int a[N],dp[N];
    
    int main(){
    	cin >> n;
    	for(int i = 0; i < n; ++i) cin >> a[i], dp[i] = 1; 
    	for(int i = 1; i < n; ++i){
    		for(int j = 0; j < i; ++j){
    			if(a[i] > a[j]) dp[i] = max(dp[i], dp[j] + 1);
    		}
    	}
    	for(int i = 0; i < n; ++i) ans = max(ans, dp[i]);
    	cout << ans;
    	return 0;
    }
    
    

    O(nlogn)代码

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e5 + 1;
    
    int n,len;
    int a[N],f[N];
    
    int find_up(int l, int r, int w){
    	while(l < r){
    		int m = (l + r) >> 1;
    		if(f[m] >= w) r = m;
    		else l = m + 1;
    	}
    	return l;
    }		 
    
    int main(){
    	cin >> n;
    	for(int i = 1; i <= n; ++i) cin >> a[i];
    	for(int i = 1; i <= n; ++i){
    		if(a[i] > f[len]) f[++len] = a[i];
    		else{
    			int j = find_up(1, len, a[i]);//寻找上升序列的拆入位置
    			f[j] = a[i];
    		}
    	}
    	cout << len;			 
    	return 0;
    }
    

    导弹拦截代码

    //寻找非严格单调递减和严格递增子序列
    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e5 + 1;
    
    int n,len;
    int a[N],dp[N];
    
    int find_up(int l, int r, int w){	
    	while(l < r){
    		int m = (l + r) >> 1;
    		if(dp[m] >= w) r = m;
    		else l = m + 1;
    	}
    	return l;
    }
    
    int find_down(int l, int r, int w){	
    	while(l < r){
    		int m = (l + r) >> 1;
    		if(dp[m] < w) r = m;
    		else l = m + 1;
    	}
    	return l;
    }
    
    int main(){
    	while(cin >> a[++n]); --n;
    	dp[++len] = a[1];	
    	for(int i = 2; i <= n; ++i){
    		if(a[i] <= dp[len]) dp[++len] = a[i];
    		else{
    			int j = find_down(1, len, a[i]);
    			dp[j] = a[i];
    		}
    	}
    	cout << len << endl;
    	len = 0;	
    	for(int i = 1; i <= n; ++i){
    		if(a[i] > dp[len]) dp[++len] = a[i];
    		else{
    			int j = find_up(1, len, a[i]);
    			dp[j] = a[i];			
    		}
    	}
    	cout << len;
    	return 0;
    }
    

  • 相关阅读:
    外观模式
    装饰器模式
    eclipse在运行main方法时在console里面报内存溢出的错误解决办法
    windows7安装node
    Eclipse-低版本离线集成svn步骤
    IDEA-JetBrains产品永久破解
    Java对字符串使用MD5进行加密(亲测有效)
    windows下的java项目打jar分别编写在windows与linux下运行的脚本( 本人亲测可用!)
    在linux中运行main方法所在的java类(亲测有效!!!)
    linux常用命令
  • 原文地址:https://www.cnblogs.com/Adventurer-H/p/11304215.html
Copyright © 2020-2023  润新知