DP 经典问题(六)多重部分和问题

来源:互联网 发布:noip复赛算法总结 编辑:程序博客网 时间:2024/05/17 04:44

问题描述:

有n种不同大小的数字ai,每种各mi个,判断是否可以从这些数字之中选出若干使它们的和恰好为K。(1 <=n <= 100 , 1 <= ai,mi <=100000 , 1 <= K <= 100000)
输入:

n = 3 ,K = 17a = { 3 , 5 , 8}m = { 3 , 2 , 2}

输出

Yes(3*3+8=17}

分析:

  • 1.刻画一个最优解的结构特征:
    定义dp[i+1][j]为前 i 个数是否能加和成 j

  • 2.递归地定义最优解的值:
    为了让前 i 个数加和成j,那么前 i-1个数就需要加和成 j , j - ai , …. ,j - mi*ai中的某一种。
    由此,递推关系如下:
    dp[i+1][j]=(0 <= k <=mi且k*ai <= j时存在使dp[i][j-k*a]为真的k)

  • 3.计算最优解的值,采用自底向上的递推法。
    代码如下:

#include<cstdio>using namespace std;const int maxn = 100;int n,K;int a[maxn],m[maxn];bool dp[maxn][maxn];//dp[i+1][j]:用前i种数字是否能加和为jvoid solve(){    dp[0][0] = true;//没有数字加和当然为0了    for(int i=0;i<n;i++)    {        for(int j=0;j<=K;j++)        {            for(int k=0;k<=m[i]&&k*a[i]<=j;k++)            {                dp[i+1][j] |= dp[i][j-k*a[i]];//"|="为位操作运算符——位或             }        }    }    if(dp[n][K]) printf("Yes\n");    else printf("No\n");}int main(){    scanf("%d%d",&n,&K);    for(int i=0;i<n;i++)    scanf("%d",&a[i]);    for(int i=0;i<n;i++)    scanf("%d",&m[i]);    solve();    return 0;}

这个算法时间复杂度为O(K∑imi),所以还需要优化

优化:

  • 1.刻画一个最优解的结构特征:
    重新定义dp[i+1][j]为前i种数加和得到 j 时第 i 种数最多还能剩余多少个(不能加和得到 i 的情况为 -1)

  • 2.递归地定义最优解的值:
    如果前 i-1个数加和得到 j 的话,那么第 i 个数就不用加了,就剩下mi个。
    如果前 i 种数加和出 j - ai时第 i 种数还剩下k的话,用这 i 种数加和 j 时第 i 种数就能剩下k - 1个。
    dp[i+1][j]=

    • 1.mi (dp[i][j]>=0) ;
    • 2.-1 (j < ai 或者dp[i+1][j-ai]<=0) ;
    • 3.dp[i+1][j-ai]-1 (其他);
  • 3.计算最优解的值,采用自底向上的递推法。
    代码如下:

#include<cstdio>#include<cstring>using namespace std;const int maxn = 100000+10;int dp[maxn],a[maxn],m[maxn];//dp[i+1][j]为用前i种数加和得到j时第i种数最多能剩余多少个 int main(){    int n,K;    scanf("%d%d",&n,&K);    for(int i=0; i<n; i++)    {        scanf("%d",&a[i]);    }    for(int i=0; i<n; i++)    {        scanf("%d",&m[i]);      }    memset(dp,-1,sizeof(dp));    dp[0] = 0;    for(int i = 0; i<n; i++)    {        for(int j=0; j<=K; j++)        {            if(dp[j] >= 0)            {                dp[j] = m[i];//如果前i-1个数加和能得到j的话,第i个数就可以留下mi个             }else if(j < a[i] || dp[j - a[i]]<=0)            {                dp[j] = -1;            }else{                dp[j] = dp[j-a[i]] - 1;            }         }    }    if(dp[K]>=0) printf("Yes\n");    else printf("No\n");    return 0;}
0 0
原创粉丝点击