题目描述:
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
n≤100,m≤100000,Ai≤100000,Ci≤1000
题目分析:
题面显然是要求一个多重背包。
- 方法一:二进制优化。
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] x−A[i]已经被凑出并且 c n t [ x − A [ i ] ] < C [ i ] cnt[x-A[i]]<C[i] cnt[x−A[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[x−A[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[x−A[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=1∑Cift−1[x−k∗Ai]( ∑ \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] ft−1[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);
}
}