硬币购物「容斥+背包」

硬币购物「容斥+背包」

题目描述

共有 \(4\) 种硬币。面值分别为 \(c_1,c_2,c_3,c_4\)

某人去商店买东西,去了 \(n\) 次,对于每次购买,他带了 \(d_i\)\(i\) 种硬币,想购买 \(s\) 的价值的东西。请问每次有多少种付款方法。

输入格式

输入的第一行是五个整数,分别代表 \(c_1,c_2,c_3,c_4, n\)

接下来 \(n\) 行,每行有五个整数,描述一次购买,分别代表 \(d_1, d_2, d_3, d_4,s\)

输出格式

对于每次购买,输出一行一个整数代表答案。

输入输出样例

输入 #1

1 2 5 10 2
3 2 3 1 10
1000 2 2 2 900

输出 #1

4
27

数据规模与约定

对于 \(100\%\) 的数据,保证 \(1≤c_i​,d_i​,s≤10^5,1≤n≤1000\)

思路分析

  • 上去就跑了个暴力,结果直接 T 到飞起,貌似全是大数据
  • 所以如果要跑背包,那么时间限制是只允许我们跑一遍的,这时候的答案就需要换一个巧妙的方法来得到
  • 每次询问的不同在于每种硬币的限制个数不同,所以我们为了避免受其影响,就可以这样计算答案:
    合法方案数 = 无个数限制的方案数 - 不合法的方案数
  • 对于无个数限制的方案数,就是一个简单的完全背包,直接预处理就好了,关键在于我们如何迅速得出不合法的方案数:
    • 因为只有四种硬币,所以我们可以枚举出,有哪些硬币超过了个数限制从而导致方案不合法
    • 这时可以直接让选出的硬币选 \((d[i]+1)\) 个,即保证超过个数限制,不合法的方案数就是 \(f[s-c[i]*(d[i]+1)]\)
  • 然而不同的情况会有交集,这时候就要用到容斥原理:
    • 设四个硬币超过个数限制的方案分别为 \(A,B,C,D\),依据容斥原理就有 \(f[A∪B] = f[A]+f[B]-f[A∩B]\),其中 \(f[A] = f[s-c[1]*(d[1]+1)]\),其他同理
    • 首先 \(f[s] -= f[A]+f[B]+f[C]+f[D]\),然而两两之间会出现交集,所以还需要再加上两两的交集,这些交集之间还会有交集,说明加重了,还需要再减去交集的交集,以此类推(禁止套娃)
  • 最终用状压枚举所有集合,根据上面的推导可以发现一个性质,有奇数个元素的集合需要减去,有偶数个元素的需要加上

\(Code\)

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 100010
#define R register
#define int long long
using namespace std;
inline int read(){
	int x = 0,f = 1;
	char ch = getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
int n,s,c[10],d[10],f[N];
void pre(){
	f[0] = 1;
	for(R int i = 1;i <= 4;i++){
	    for(R int j = c[i];j <= N;j++){
		    f[j] += f[j-c[i]];
	    }
	}
}
void solve(){
    int ans=f[s];
    for(R int i=1;i<=15;i++){//状压枚举集合内元素
	int tot=0,cnt=0;
	for(R int j=0;j<4;j++){
	    if(i&(1<<j)){//在集合内则强制让其超过个数限制
	        cnt++;//记录集合数
		tot+=c[j+1]*(d[j+1]+1);
	    }
	}
	int flag = (cnt%2) ? -1 : 1;
	if(tot<=s) ans+=flag*f[s-tot];
    }
    printf("%lld\n",ans);
}
signed main(){
	c[1] = read(),c[2] = read(),c[3] = read(),c[4] = read();
	n = read();
	pre();
	for(R int i = 1;i <= n;i++){
            for(R int i=1;i<=4;i++) d[i]=read();
            s=read();
 	    solve();
	}
	return 0;
}
posted @ 2020-09-08 17:19  HH_Halo  阅读(179)  评论(0编辑  收藏  举报