zoukankan      html  css  js  c++  java
  • 硬币购物「容斥+背包」

    硬币购物「容斥+背包」

    题目描述

    共有 (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
    ",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;
    }
    
  • 相关阅读:
    (简单) POJ 3414 Pots,BFS+记录路径。
    (简单) POJ 3087 Shuffle'm Up,枚举。
    (简单) POJ 3126 Prime Path,BFS。
    (简单) POJ 1426 Find The Multiple,BFS+同余。
    (简单) POJ 3279 Fliptile,集合枚举。
    (简单) POJ 1278 Catch That Cow,回溯。
    (简单) POJ 2251 Dungeon Master,BFS。
    (简单) POJ 1321 棋盘问题,回溯。
    回溯---输出二叉树中所有从根到叶子的路径
    回溯---在矩阵中寻找字符串
  • 原文地址:https://www.cnblogs.com/hhhhalo/p/13633817.html
Copyright © 2011-2022 走看看