• 【BZOJ】1042: [HAOI2008]硬币购物(dp+容斥原理)


    http://www.lydsy.com/JudgeOnline/problem.php?id=1042

    一开始写了个O(nv)的背包,果断tle。。。

    看了题解,,好神。。用了组合数学中的多重集合方案的容斥原理。

    设$A_i$表示i超过d[i]的性质

    则我们要求:

    $$| overline{A_1} cap overline{A_2} cap ... cap overline{A_n} |$$

    而我们可以根据容斥求出这个值,即:

    $$| overline{A_1} cap overline{A_2} cap ... cap overline{A_n} | = | S | - (| A_1 | + | A_1 | + ... + | A_n |) + (|A_1 cap A_2| + |A_1 cap A_3| + ... + |A_{n-1} cap A_n|) - ...$$

    本题中,$|S|$就是硬币有无限制的数量来放满s元钱,这个用完全背包来搞就行了。而容斥的其它部分就有些神奇。

    首先处理完完全背包的数组f[i]后,我们可以观察,怎么将$A_i$算出来?假如有$d_i$个i硬币,容量是s的背包,思考为什么会多出方案?

    可以发现,多出方案的情况是每一种方案里必定有$d_i+1$枚硬币,那么可以看做是,在所有容量为$s-(d_i+1)*c_i$的背包的方案添加$d_i+1$枚i硬币,这样就保证了至少有$d_i+1$枚硬币。那么可以完美得出了$A_i$。而性质$A_i$之间的交集其实就是$s-(d_i+1)*c_i-(d_j+1)*c_j-...-(d_k+1)*c_k$,这个很容易看出来吧。。

    然后问题就解决了。

    2015.4.19 upd:

    设Ai为第i种硬币数量超过Di的性质,则问题就是求:
    Ai补的交集
    那么也就是容斥一下答案要求的性质就是:
    全集S-(Ai)+(Ai交Aj)-...
    令f[i]表示恰好花费i的完全背包方案数。
    发现Ai的性质就是:已经在背包里放了至少Di+1个i物品,然后剩下的背包随便放的方案,即f[s-(Di+1)*Ci]!
    然后求这些性质的交集,发现其实和上面分析一样!Ai与Aj的交集的方案就是f[s-(Di+1)*Ci-(Dj+1)*Cj]!
    于是容斥一下就行了!!!

    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <string>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    #include <set>
    #include <map>
    using namespace std;
    typedef long long ll;
    #define pii pair<int, int>
    #define mkpii make_pair<int, int>
    #define pdi pair<double, int>
    #define mkpdi make_pair<double, int>
    #define pli pair<ll, int>
    #define mkpli make_pair<ll, int>
    #define rep(i, n) for(int i=0; i<(n); ++i)
    #define for1(i,a,n) for(int i=(a);i<=(n);++i)
    #define for2(i,a,n) for(int i=(a);i<(n);++i)
    #define for3(i,a,n) for(int i=(a);i>=(n);--i)
    #define for4(i,a,n) for(int i=(a);i>(n);--i)
    #define CC(i,a) memset(i,a,sizeof(i))
    #define read(a) a=getint()
    #define print(a) printf("%d", a)
    #define dbg(x) cout << (#x) << " = " << (x) << endl
    #define error(x) (!(x)?puts("error"):0)
    #define printarr2(a, b, c) for1(_, 1, b) { for1(__, 1, c) cout << a[_][__]; cout << endl; }
    #define printarr1(a, b) for1(_, 0, b) cout << a[_] << '	'; cout << endl
    inline const int getint() { int r=0, k=1; char c=getchar(); for(; c<'0'||c>'9'; c=getchar()) if(c=='-') k=-1; for(; c>='0'&&c<='9'; c=getchar()) r=r*10+c-'0'; return k*r; }
    inline const int max(const int &a, const int &b) { return a>b?a:b; }
    inline const int min(const int &a, const int &b) { return a<b?a:b; }
    
    const int N=100005;
    ll f[N], ans;
    int n, c[5], d[5], s;
    
    inline void cmp(int v) { for1(i, v, s) f[i]+=f[i-v]; }
    void dfs(int dep, int x, int sum) {
    	if(dep==5) {
    		if(s-sum<0) return;
    		if(x&1) ans-=f[s-sum];
    		else ans+=f[s-sum];
    		return;
    	}
    	dfs(dep+1, x, sum);
    	dfs(dep+1, x+1, sum+d[dep]);
    }
    int main() {
    	for1(i, 1, 4) read(c[i]); int tot=getint();
    	f[0]=1;
    	s=100005;
    	for1(i, 1, 4) cmp(c[i]);
    	while(tot--) {
    		for1(i, 1, 4) read(d[i]);
    		for1(i, 1, 4) d[i]=(d[i]+1)*c[i];
    		read(s);
    		ans=0;
    		dfs(1, 0, 0);
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    

      


    Description

    硬币购物一共有4种硬币。面值分别为c1,c2,c3,c4。某人去商店买东西,去了tot次。每次带di枚ci硬币,买si的价值的东西。请问每次有多少种付款方法。

    Input

    第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,s

    Output

    每次的方法数

    Sample Input

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

    Sample Output

    4
    27

    HINT

    数据规模

    di,s<=100000

    tot<=1000

    Source

     
  • 相关阅读:
    mysql 7.5.8 服务无法启动 服务没有报告任何错误
    Ubuntu 16.04 php卸载
    函数式编程(3)-匿名函数
    函数式编程(2)-返回函数
    函数式编程(1)-高阶变成(3)-sorted
    函数式编程(1)-高阶变成(2)-filter
    函数式编程(1)-高阶变成(1)-map/reduce
    高级特性(4)-生成器
    高级特性(3)-列表生成式
    高级特性(2)-迭代
  • 原文地址:https://www.cnblogs.com/iwtwiioi/p/4095497.html
Copyright © 2020-2023  润新知