动态规划问题入门

学动态规划是在写蓝桥杯题目的时候,遇到了一道题目,需要动态规划的知识。
在蓝桥杯,对局匹配题目中:http://lx.lanqiao.cn/detail.page?submitid=5016804
需要运用到对局匹配的知识。
定义
动态规划就是通过拆分问题,已达到分治的目的。通过你将待求解的问题拆分成若干个子问题,通过求解前一个子问题的解来为后面的子问题提供有效信息。在求最优解时,通过把前一个子问题解的最优解求出来后,抛弃其他的解,以此来通过前一个问题的最优解来不断向后求解最优解,直到最后一个最优解出来后,就是初始问题的解。
对于重叠子问题(over-lap)的回避
由于在拆分若干个子问题后,如果我们采用递归的方式来解决每个子问题的话,我们会发现通过递归的方式的话,在某个数值之后就是重复性的递归,便会造成重叠子问题,其时间复杂度就是指数型的复杂度了。
为了避免重叠子问题的产生,我们可以使用数组对已经算出了的子问题进行存储,避免重叠子问题的产生。
对于初始问题的拆分和推导
***1.***在拆分时,选取一个拆分变量来进行拆分,并且把握住拆分变量之间的内在关系。
***2.***状态转移方程式:对于动态规划题目,dp[i]=max{A,B};或者是dp[i]=min{A,B}。根据题目的要求,我们对子问题数组dp[]进行最优化选择,直到回归最初的问题本身。
例题分析
1.
这个问题直接给出了一段求函数w(a, b, c)的伪代码:
function w(a, b, c):
if a <=0 or b <=0 or c <=0, then returns:1
if a >20or b >20or c >20, then returns: w(20,20,20)
if a < b and b < c, then returns: w(a, b, c-1)+ w(a, b-1, c-1)- w(a, b-1, c)
otherwise it returns: w(a-1, b, c)+ w(a-1, b-1, c)+ w(a-1, b, c-1)-w(a-1,b-1,c-1);
要求给定a, b, c,求w(a, b, c)的值。
乍看下只要将伪代码翻译成实际代码,然后直接对于给定的a, b, c,调用函数w(a, b, c)就能得到值了。但是只要稍加分析就能看出这个函数的时间复杂度是指数级的(尽管这个三元组的最大元素只有20,这是个陷阱)。对于任意一个三元组(a, b, c),w(a, b, c)可能被计算多次,而对于固定的(a, b, c),w(a, b, c)其实是个固定的值,没必要多次计算,所以只要将计算过的值保存在f[a][b][c]中,这道题就是典型需要动态规划的题目了。
下面的代码是直接翻译的题目所组成的答案,很明显,其时间复杂度是很大的,不可取。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int arr[25][25][25];
int w(int a,int b,int c)
{
    
    
    if(a<=0||b<=0||c<=0)
        return 1;
    else if(a>20||b>20||c>20)
        return w(20,20,20);
    else if(a<b&&b<c)
        return w(a,b,c-1)+w(a,b-1,c-1)-w(a,b-1,c);
    else
        return w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1);
}
int main()
{
    
    
    int a,b,c;
    cin>>a>>b>>c;
    int ans=w(a,b,c);
    cout<<ans<<endl;
    return 0;
}

下面的代码就是采用了动态规划思想的代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define LL long long
using namespace std;
LL arr[21][21][21]={
    
    0};
LL w_dp(LL a,LL b,LL c)//动态规划,利用arr数组来对已经算出的进行存储,避免重复子问题的出现
{
    
    
    if(a<=0||b<=0||c<=0)
        return 1;
    if(a>20||b>20||c>20)
        return 1048576;
    if(arr[a][b][c])//如果已经有在a,b,c处的值,则直接返回,避免overlap-sub
        return arr[a][b][c];//前面三个出口
    if(a<b&&b<c)//对arr[a][b][c]进行更新
        return arr[a][b][c]=w_dp(a,b,c-1)+w_dp(a,b-1,c-1)-w_dp(a,b-1,c);
    else
        return arr[a][b][c]=w_dp(a-1,b,c)+w_dp(a-1,b-1,c)+w_dp(a-1,b,c-1)-w_dp(a-1,b-1,c-1);
}
int main()
{
    
    
    LL a,b,c;
    while(cin>>a>>b>>c)
    {
    
    
        if(a==-1&&b==-1&&c==-1)
            break;
        LL ans=w_dp(a,b,c);
        cout<<"w("<<a<<','<<b<<','<<c<<')'<<'='<<ans<<endl;
    }
    return 0;
}

上述代码通过arr数组进行存储,以此来达到对重叠子问题的规避,使得时间复杂度大幅降低

2.
判断输入的能否成为组合

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
const int aim=9;//当aim=9时,为true,当为13时,为no
int arr[]={
    
    3,34,4,12,5,2};
int subset[sizeof(arr)/4][aim+1];
int judge=0;
int subset_dp(int *arr,int number)//非递归
{
    
    
    for(int i=0;i<=number-1;i++)
        subset[i][0]=1;
    subset[0][arr[0]]=1;
    for(int i=1;i<=number-1;i++)
        for(int s=1;s<=aim;s++)
        {
    
    
            if(arr[i]>s)
                subset[i][s]=subset[i-1][s];
            else
            {
    
    
                int A=subset[i-1][s-arr[i]];
                int B=subset[i-1][s];
                subset[i][s]=A||B;
            }
        }
    return subset[number-1][aim];
}
int main()
{
    
    
    //judge=subset(arr,sizeof(arr)/4-1,aim);
    memset(subset,0,sizeof(subset));
    int number=sizeof(arr)/4;
    judge=subset_dp(arr,number);
    if(judge==1)
        cout<<"Ture"<<endl;
    else
        cout<<"No"<<endl;
    return 0;
}

以上两道题都是动态规划思想中最为基础的类型,大家可以多琢磨,琢磨

猜你喜欢

转载自blog.csdn.net/malloch/article/details/108785654