【BZOJ】1042: [HAOI2008]硬币购物
1042: [HAOI2008]硬币购物
Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 3307 Solved: 2075
[Submit][Status][Discuss]
Description
硬币购物一共有4种硬币。面值分别为c1,c2,c3,c4。某人去商店买东西,去了tot次。每次带di枚ci硬币,买s
i的价值的东西。请问每次有多少种付款方法。
Input
第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,s,其中di,s<=100000,tot<=1000
Output
每次的方法数
Sample Input
1 2 5 10 2
3 2 3 1 10
1000 2 2 2 900
3 2 3 1 10
1000 2 2 2 900
Sample Output
4
27
27
HINT
Source
时隔多年(?)终于搞懂了第一道容斥题QwQ!!特此纪念。
首先我们可以做一次完全背包,每种硬币无限制地用,统计出方案数。然后我们就会发现多计入了一些不合法的情况,就是第$i$种硬币用了超出$d[i]$的数量的方案数。我们要统计所有不合法的情况,就是第一种硬币不合法的方案数+第二种硬币不合法的方案数+第三种硬币+第四种硬币-第一和第二-第二和第三...这就是奇加偶减的容斥!而我们是用所有情况减去不合法的情况,在$dfs$容斥中反过来就可以了。
【注意】$ans$最开始是0,因为在容斥中就会走到每一种硬币不合法的情况都不减去的情况,这时就是所有情况的总数。
代码中的$k$表示的就是当前减去了几个物品不合法的数量,奇加偶减。
$sum$是当前剩余需要填满的钱数,那么$f[sum-(d[i]+1)*c[i]]$表示的是第$i$种钱币用了$d[i]+1$填满$sum$的方案数,即这个硬币使用不合法的方案数。(强制使第$i$种硬币不合法
#include<iostream> #include<cstdio> #define ll long long using namespace std; int c[5], tot, d[5]; ll ans, f[100005]; void dfs ( int dep, int k, int sum ) { if ( sum < 0 ) return ; if ( dep == 5 ) { if ( k & 1 ) ans -= f[sum]; else ans += f[sum]; return ; } dfs ( dep + 1, k + 1, sum - ( d[dep] + 1 ) * c[dep] ); dfs ( dep + 1, k, sum ); } int main ( ) { for ( int i = 1; i <= 4; i ++ ) scanf ( "%d", &c[i] ); scanf ( "%d", &tot ); f[0] = 1; for ( int i = 1; i <= 4; i ++ ) for ( int j = c[i]; j <= 100000; j ++ ) f[j] += f[j-c[i]]; while ( tot -- ) { for ( int i = 1; i <= 4; i ++ ) scanf ( "%d", &d[i] ); int s; scanf ( "%d", &s ); ans = 0; dfs ( 1, 0, s ); printf ( "%lld\n", ans ); } return 0; }