AOJ-743-多重部分和问题

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

Input
首先是一个正整数T(1<=T<=100)
接下来是T组数据
每组数据第一行是一个正整数n(1<=n<=100),表示有n种不同大小的数字
第二行是n个不同大小的正整数ai(1<=ai<=100000)
第三行是n个正整数mi(1<=mi<=100000),表示每种数字有mi个
第四行是一个正整数K(1<=K<=100000)

Output
对于每组数据,如果能从这些数字中选出若干使它们的和恰好为K,则输出“Yes”,否则输出“No”,每个输出单独占一行

Sample Input
2
3
3 5 8
3 2 2
17
2
1 2
1 1
4

Sample Output
Yes
No

Source
安徽省2015年“京胜杯”大学生程序设计竞赛

—————————并不华丽的分界线————————————
很明显,这是一个入门的多重背包问题。
对于背包问题,想要了解的更多的可以去看看背包九讲,或者是2009年国家集训队论文《浅谈几类背包问题》(作者是浙江省温州中学徐持衡)
对于这个背包问题,很明显只需要使用普通的解法就可以了,也就是把每种物品的数量分成1,2,4,8…..V[i] - 2^n -1(V[i]表示第i个物品的数量,其价值为A[i])就可以了。
在做多重背包时,第一个想法必然是把多重背包转化为01背包,但是这个时候,它的时间复杂度就会比较高,可能能达到O(NM∑Vi),那么这个算法就不是很可取了,但是如果考虑把V[i]化成V[i] = 1 + 2 + 4 + …. + V[i]-2^n-1且每个物品的价值也以此来改变,那么其时间复杂度可以大大优化。当然,如果想要获得更大的优化,可以使用单调队列优化。
所以这道题就非常容易了,代码如下:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int maxn = 100010;
int a[maxn];
int m[maxn];
int f[maxn];

bool judge(int n, int k){
    for(int i = 0; i <= k; i++){
        f[i] = 0;
    }
    for(int i = 0; i < n; i++){
        int t = 1;
        while(m[i] > 0){
            if(t > m[i]){
                t = m[i];
            }
            m[i] = m[i] - t;

            for(int j = k; j >= a[i] * t; j--){
                f[j] = max(f[j], f[j - t * a[i]] + t * a[i]);
            }
            t *= 2;
        }
    }
    for(int j = 0; j <= k; j++){
        if(f[j] == k)
            return true;
    }
    return false;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        int n, k;
        scanf("%d",&n);
        for(int i = 0; i < n; i++){
            scanf("%d",&a[i]);
        }
        for(int i = 0; i < n; i++){
            scanf("%d",&m[i]);
        }
        scanf("%d",&k);
        if(judge(n, k)){
            printf("Yes\n");
        }else{
            printf("No\n");
        }
    }
    return 0;
}

本人渣渣,如有哪里错误,请各位大牛指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值