挑战程序设计竞赛 多重部分和问题

就类似完全背包。。不过要恰好等于背包容量。。

就是给n种数ai,各mi个,判断是否能从这些数字中选出若干个使他们的和为v。

用dp求解,dp[i+1][j]表示前i种数字能否加和得到j。dp[i+1][j] 只要dp[i][j-k*ai]有一个为真那么就为真。(0<=k<=mi)

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define M 109
int a[M],m[M];
bool dp[M][100009];
int main()
{
    int n;
    while(scanf("%d",&n)==1)
    {
        memset(dp,false,sizeof(dp));
        for(int i = 0;i < n;i++)
            scanf("%d",&a[i]);
        for(int i = 0;i < n;i++)
            scanf("%d",&m[i]);
        int v;
        scanf("%d",&v);
        dp[0][0] = true;
        for(int i = 0;i < n;i++)
        {
            for(int j = 0;j <= v;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][v]) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

这样可以看出时间复杂度为O(v*(m1+```+mi))

书上说用dp数组只用来求bool结果,会造成浪费。给出了一个优化,dp[i+1][j]表示前i种数字中加和为j后还剩下几个第i种数字(如果不能加和为J 就为-1)

当dp[i][j]>=0时,dp[i+1][j] = mi 当dp[i+1][j-ai]<=0或者j<ai(防止数组下标变为负数)时 dp[i+1][j] = -1   其他情况下,dp[i+1][j] = dp[i+1][j-ai]-1

给出代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define M 109
int a[M],m[M];
int dp[M][100009];
int main()
{
    int n;
    while(scanf("%d",&n)==1)
    {
        memset(dp,-1,sizeof(dp));
        for(int i = 0;i < n;i++)
            scanf("%d",&a[i]);
        for(int i = 0;i < n;i++)
            scanf("%d",&m[i]);
        int v;
        scanf("%d",&v);
        dp[0][0] = 0;
        for(int i = 0;i < n;i++)
        for(int j = 0;j <= v;j++)
        {
            if(dp[i][j]>=0) dp[i+1][j] = m[i];
            else if(j<a[i] || dp[i+1][j-a[i]]<=0) dp[i+1][j] = -1;//一定注意要防止数组下标变为0
            else dp[i+1][j] = dp[i+1][j-a[i]]-1;
        }
        if(dp[n][v]>=0) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}
优化成一维:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define M 109
int a[M],m[M];
int dp[100009];
int main()
{
    int n;
    while(scanf("%d",&n)==1)
    {
        memset(dp,-1,sizeof(dp));
        for(int i = 0;i < n;i++)
            scanf("%d",&a[i]);
        for(int i = 0;i < n;i++)
            scanf("%d",&m[i]);
        int v;
        scanf("%d",&v);
        dp[0] = 0;
        for(int i = 0;i < n;i++)
        for(int j = 0;j <= v;j++)
        {
            if(dp[j]>=0) dp[j] = m[i];
            else if(j<a[i] || dp[j-a[i]]<=0) dp[j] = -1;//一定注意要防止数组下标变为0
            else dp[j] = dp[j-a[i]]-1;
        }
        if(dp[v]>=0) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值