HDU2844 Coins【多重背包新姿势】

题目描述:

n种硬币,面值 A i A_i Ai,个数 C i C_i Ci,求可以凑出 [ 1 , m ] [1,m] [1,m]中多少个数。
n ≤ 100 , m ≤ 100000 , A i ≤ 100000 , C i ≤ 1000 n\le100,m\le100000,A_i\le100000,C_i\le1000 n100,m100000,Ai100000,Ci1000

题目分析:

题面显然是要求一个多重背包。

  • 方法一:二进制优化。 O ( n m l o g C ) O(nmlogC) O(nmlogC)
    C i C_i Ci个物品拆成 2 0 + 2 1 + 2 2 . . . + r e s t 2^0+2^1+2^2...+rest 20+21+22...+rest形式的物品做01背包。
  • 方法二:记录凑出这个数最少需要多少当前物品。 O ( n m ) O(nm) O(nm)
    此题是求是否可以凑出,在添加一个物品时记录当前状态用过多少次这个物品记为 c n t [ x ] cnt[x] cnt[x],如果 x x x在上一轮没有被凑出而在这一轮 x − A [ i ] x-A[i] xA[i]已经被凑出并且 c n t [ x − A [ i ] ] < C [ i ] cnt[x-A[i]]<C[i] cnt[xA[i]]<C[i],那么 x x x在这一轮就可以被凑出,同时令 c n t [ x ] = c n t [ x − A [ i ] ] + 1 cnt[x]=cnt[x-A[i]]+1 cnt[x]=cnt[xA[i]]+1
  • 方法三:统计方案数。 O ( n m ) O(nm) O(nm)(常数比方法二大)
    记这一轮的背包数组为 g g g,上一轮为 f f f,那么 g [ x ] = f [ x ] + g [ x − A [ i ] ] − f [ x − ( C [ i ] + 1 ) ∗ A [ i ] ] g[x]=f[x]+g[x-A[i]]-f[x-(C[i]+1)*A[i]] g[x]=f[x]+g[xA[i]]f[x(C[i]+1)A[i]],相当于减去用了 C [ i ] + 1 C[i]+1 C[i]+1个当前物品的方案数。此题没有模数可以采用自然溢出。\
  • 方法四:单调队列优化( O ( n m ) O(nm) O(nm)且是通用方法)
    注意到 f t [ x ] = ∑ k = 1 C i f t − 1 [ x − k ∗ A i ] f_t[x]=\sum\limits_{k=1}^{C_i}f_{t-1}[x-k*A_i] ft[x]=k=1Cift1[xkAi] ∑ \sum 是或的意思,找不到好的符号了, t t t表示考虑前 t t t个物品),对于 x   m o d   k x~mod~k x mod k余数相同的 x x x可以看做连续的序列,那么对于 x   m o d   k = y x~mod~k=y x mod k=y的每个 y y y,存当前最大的 f t − 1 [ p k + y ] f_{t-1}[pk+y] ft1[pk+y] p p p,然后就可以 O ( 1 ) O(1) O(1)检验了。

Code1:见这里
Code2:

#include<bits/stdc++.h>
#define maxn 115
#define maxm 100015
using namespace std;
int n,m,A[maxn],C[maxn],cnt[maxm];
bool f[maxm];
int main()
{
    while(~scanf("%d%d",&n,&m)&&n+m){
        for(int i=1;i<=n;i++) scanf("%d",&A[i]);
        for(int i=1;i<=n;i++) scanf("%d",&C[i]);
        memset(f,0,sizeof f),f[0]=1;
        int ans=0;
        for(int i=1;i<=n;i++){
            memset(cnt,0,(m+1)<<2);
            for(int j=A[i];j<=m;j++)
                if(!f[j]&&f[j-A[i]]&&cnt[j-A[i]]<C[i])
                    f[j]=1,cnt[j]=cnt[j-A[i]]+1,ans++;
        }
        printf("%d\n",ans);
    }
}

Code3:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 105
#define maxm 100005
using namespace std;
int n,m,A[maxn],C[maxn];
int a[2][maxm],*f=a[0],*g=a[1];
int main()
{
    while(~scanf("%d%d",&n,&m)&&n){
        for(int i=1;i<=n;i++) scanf("%d",&A[i]);
        for(int i=1;i<=n;i++) scanf("%d",&C[i]);
        memset(f,0,(m+1)<<2),f[0]=1;
        for(int i=1;i<=n;i++,swap(f,g)){
            memcpy(g,f,A[i]<<2);
            for(int j=A[i];j<=m;j++)
                g[j]=g[j-A[i]]+f[j]-(j>=(C[i]+1)*A[i]?f[j-(C[i]+1)*A[i]]:0);
        }
        int ans=0;
        for(int i=1;i<=m;i++) if(f[i]) ans++;
        printf("%d\n",ans);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值