[HAOI2008]硬币购物(容斥/背包DP)

题目

题目地址

题解

先跑一遍完全背包,然后对于第 \(i\) 种硬币只能用 \(d_i\) 枚容斥一下。具体的:(强制 \(k\) 个硬币超出限制)

\[\sum_{T \subset D,|T|=k} (-1)^k \sum_{j \in T} f[s-c_i(d_i+1)] \]

(其中,\(D\)\(4\) 种硬币构成的集合,\(f[i]\) 是完全背包后,\(i\) 体积有多少种凑成方式)

时间复杂度 \(O(ns)\)

代码

Talk is cheap.Show me the code.

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
    int x = 0, f = 1; char ch = getchar();
    while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
    return x * f;
}
const int N = 1e5+7;
int s;
int c[N],d[N],f[N];
void work() {
	memset(f, 0, sizeof(f));
	for(int i=0;i<4;++i) d[i] = read();
	s = read();
	f[0] = 1;
	for(int i=0;i<4;++i) {
		for(int j=c[i];j<=s;++j) {
			f[j] += f[j-c[i]];
		}
	}
	int up = (1<<4) - 1;
	int ans = 0;
	for(int i=0;i<=up;++i) {
		int lim = 0, sz = 0;
		for(int j=0;j<4;++j) {
			if(i & (1<<j)) {
				lim += c[j]*(d[j]+1);
				++sz;
			}
		}
		if(lim <= s) ans += (sz&1 ? -1 : 1) * f[s-lim];
	}
	printf("%lld\n",ans);
}
signed main()
{
	for(int i=0;i<4;++i) c[i] = read();
	int T = read();
	while(T--) work();
	return 0;
}
/*
1 2 5 10 2
3 2 3 1 10
1000 2 2 2 900

4
27
*/

总结

背包,容斥的灵活运用。

posted @ 2020-12-01 19:58  基地AI  阅读(107)  评论(0编辑  收藏  举报