• 多重背包(dp专题)


    题目大意:输入n,代表有n种数,接下来n个数代表n种数,再接下来n个数代表每种数有多少个,在输入K,代表用这些数要加成的和

    问你是否能加为K,能输出yes,不能输出no

    这是一个典型的多重背包问题,可以用dp来求解,。但是如何定义递推关系会影响到最终的复杂度,首先我们先看一下如下定义:

    dp[i+1][j];=用前i种数能否加成和为j

    为了用前i种数加成j,也就需要能用前i-1种数字加成j,j-a[i],···,j-mi*a[i],中的某一种,由此我们可以定义如下递推关系

    dp[i+1][j]=(0<=k<=mi,且k*a[i]<=j时,存在使dp[i][j-k*a[i]]为真的k;

    看代码

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    #include<cmath>
    #include<math.h>
    #include<algorithm>
    #include<set>
    #include<queue>
    typedef long long ll;
    using namespace std;
    const ll mod=1e9+7;
    #define INF 0x3f3f3f
    bool dp[110][100050];
    int main()
    {
        memset(dp,false,sizeof(dp));
        dp[0][0]=true;//赋初值
        int n,s;
        int a[100050],b[100050];
        cin>>n;
        for(int i=0;i<n;i++)
        {
            cin>>a[i];
        }
        for(int i=0;i<n;i++)
            cin>>b[i];
        cin>>s;
       //for(int i=0;i<n;i++)
         // cout<<a[i]<<' '<<b[i]<<endl;
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<=s;j++)
            {
                for(int k=0;k<=b[i]&&k*a[i]<=j;k++)
                {
                    dp[i+1][j]|=dp[i][j-k*a[i]];//注意这里的或运算,代表有一个为真则为真
                }
            }
        }
       //for(int i=0;i<=s;i++)
         //   cout<<dp[n][i]<<' ';
        if(dp[n][s])
            cout<<"yes"<<endl;
        else
            cout<<"no"<<endl;
        return 0;
    }

     上面这个算法的复杂度是比较大,并不够好。一般用dp求取bool 结果的话会有不少浪费,同样的复杂度通常能获得更多的信息

    在这个问题中,我们不光求出能否得到目标的和数,同时把得到时a[i]这个数还剩多少个计算出来,这样就可以减少复杂度

    dp[i+][j]:=用前i种数加和得到j时,第i种数还剩多少个(不能加的情况为-1)

    按照如上所述的递推关系,这样如果前i-1个数加能得到j的话,第i个数就可以留下b[i]个了,此外,前i种数加和出j-a[i]时第i种数还剩k(k>0)个的话

    ,用这i种数加和为j时就能剩k-1个了,由此我们可以得出如下递推式:

    dp[i+1][j]=b[i]     (dp[i][j]>=0)

    dp[i+1][j]=-1       (j<a[i]||dp[i_1][j-a[i]]<=0)

    dp[i+1][j]=dp[i+1][j-a[i]]-1     (其它)

    看代码

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    #include<cmath>
    #include<math.h>
    #include<algorithm>
    #include<set>
    #include<queue>
    typedef long long ll;
    using namespace std;
    const ll mod=1e9+7;
    #define INF 0x3f3f3f
    int dp[110][100050];
    int main()
    {
        memset(dp,-1,sizeof(dp));
        dp[0][0]=0;//赋初值
        int n,s;
        int a[100050],b[100050];
        cin>>n;
        for(int i=0;i<n;i++)
        {
            cin>>a[i];
        }
        for(int i=0;i<n;i++)
            cin>>b[i];
        cin>>s;
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<=s;j++)
            {
                if(dp[i][j]>=0)
                    dp[i+1][j]=b[i];
                else if(j<a[i]||dp[i+1][j-a[i]]<=0)
                    dp[i+1][j]=-1;
                else
                    dp[i+1][j]=dp[i+1][j-a[i]]-1;
    
            }
        }
        if(dp[n][s]>=0)
            cout<<"yes"<<endl;
        else
            cout<<"no"<<endl;
        return 0;
    }

    题目大意:有一个长度为n的序列,a[0],a[1]···a[n-1],请求出这个序列中最长的上升子序列的长度,上升子序列可以不连续,任意i<j,a[i]<a[j]

    限制条件:1<=n<=1000   1<=a[i]<=1000000

    首先我们建立递推关系

    定义dp[i]=:以a[i]为末尾的最长上升子序列的长度

    以a[i]为末尾的上升子序列是:

    只包含a[i]的子序列

    在满足j<i并且a[j]<a[i]的以a[j]为结尾的上升子序列追加上a[i]后得到的子序列

    这二者之一。这样我们就能得到如下递推关系

    dp[i]=max(1,dp[j]+1)  j<i并且a[j]<a[i]

    使用这一递推公式可以在O(n^2)时间内解决问题

    看代码

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    #include<cmath>
    #include<math.h>
    #include<algorithm>
    #include<set>
    #include<queue>
    typedef long long ll;
    using namespace std;
    const ll mod=1e9+7;
    #define INF 0x3f3f3f
    int main()
    {
        int n,ans=0;
        int a[1100];
        int dp[1100];
        memset(dp,0,sizeof(dp));
        cin>>n;
        for(int i=0;i<n;i++)
        {
            cin>>a[i];
        }
        for(int i=0;i<n;i++)
        {
            dp[i]=1;
            for(int j=0;j<i;j++)
            {
                if(a[j]<a[i])
                    dp[i]=max(dp[i],dp[j]+1);
            }
            ans=max(ans,dp[i]);
        }
        cout<<ans<<endl;
        return 0;
    }

     此外还可以定义其它的递推关系。前面我们利用do求取针对最末尾的元素的最长的子序列。如果子序列

    长度相同,那么最末尾的元素较小的在之后会更加有优势,所以我们反过来用dp针对长度相同情况下最小

    的末尾元素进行求解

    dp[i]:=长度为i+1的上升子序列中末尾元素的最小值

    最开始全部dp[i]的值都初始化为为INF。然后由前到后逐个考虑数组的元素,对于每个a[j],如果i=0或者dp[i-1]<a[j]的话,就用dp[i]=min(dp[i],a[j])进行更新。这里如果看不懂就自己拿抄稿本走一遍,就一清二楚了。最终找出使得dp[i]<INF的最大i+1就是结果了。这个dp直接实现的话,能够与

    前面的方法一样在O(n^2)的时间内给出结果,但是这一算法还可以进一步优化。 首先dp数组里除了INF之外是单调递增的,所以可以知道对于

    每一个a[j]最多只需要一次更新。对于这次更新在什么位子,不必逐个遍历,可以利用二分搜索,这样就可以在O(nlogn)的时间内求出结果

    复杂度O(nlogn)

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    #include<cmath>
    #include<math.h>
    #include<algorithm>
    #include<set>
    #include<queue>
    typedef long long ll;
    using namespace std;
    const ll mod=1e9+7;
    #define INF 0x3f3f3f
    int main()
    {
        int n;
        int a[1100];
        int dp[1100];
        cin>>n;
        fill(dp,dp+n,INF);
        for(int i=0;i<n;i++)
        {
            cin>>a[i];
        }
        for(int i=0;i<n;i++)
        {
            *lower_bound(dp,dp+n,a[i])=a[i];
        }
        cout<<lower_bound(dp,dp+n,INF)-dp<<endl;
        return 0;
    }

     上面代码中使用了lower_bound这个STL函数。这个函数从已排好序的序列a中利用二分搜索找出满足a[i]>=k的最小的a[i]的指针。

    当初的梦想实现了吗,事到如今只好放弃吗~
  • 相关阅读:
    计算日期之差
    大数相加
    NY-字符串替换
    HDU1004之总是wa的细节问题
    指针在字符串简单应用
    mybatis~SQL映射
    java实现递归(1)
    apk、图片下载工具(1)
    签到规则工具(1)
    短信发送工具(2)
  • 原文地址:https://www.cnblogs.com/caijiaming/p/9311870.html
Copyright © 2020-2023  润新知