多重部分和问题(动态规划(DP))

注:文章内容源自《挑战程序设计竞赛》(第二版)

原题

多重部分和问题

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

样例输入


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

样例输出


Yes(3*3+8=17)

涉及知识及算法


定义dp[i+1][j]为前i种数字是否能加和成j
为了用前i种数字加和成j,也就需要能用前i-1种数字加和成j,j-ai,…,j-mi*ai中的某一种。由此我们可以定义如下递推关系:
dp[i+1][j]=(0<=k<=mi且k*ai<=j时存在使dp[i][j-k*ai]为真的k)

代码

//数列的长度
int n;
//目标的和数
int K;
//值
int a[MAX_N];
//个数
int m[MAX_N];
bool dp[MAX_N+1][MAX_K+1];

void solve()
{
    dp[0][0]=true;
    for(int i=0;i
这个算法并不够好,复杂度为O(K∑imi)。一般用DP求取bool结果会有不少浪费,同样的复杂度往往能获得更多信息。在这个问题中,我们不光求出能否得到目标的和数,同时把得到时ai这个数还剩下多少个可以使用 计算出来,这样就可以减少复杂度。
可以定义dp[i+1][j]为用前i种数加和得到j时第i种数最多能剩余多少个(不能加和得到i的情况下为-1)
这样如果前i-1个数加和能得到j的话,第i个数就可以留下mi个。此外,前i种数加和出j-ai时第i种数还剩下k(k>0)的话,用这i种数加和j时第i种数就能剩下k-1个。由此可得:
dp[i+1][j]={ mi (dp[i][j]>=0)
{ -1 (j
{ dp[i+1][j-ai]-1 (其他)
这样只要看最终是否满足dp[n][K]>=0就可以知道答案了。
这个递推式可以在O(nK)时间内计算出结果。再将数组重复利用,则可以得到:
int dp[MAX_K+1];
void solve()
{
    memset(dp,-1,sizeof(dp));
    dp[0]=0;
    for(int i=0;i=0)
            {
                dp[j]=m[i];
            }
            else if(j=0)
    {
        printf("Yes\n");
    }
    else 
    {
        printf("No\n");
    }
}




你可能感兴趣的:(动态规划(DP))