BZOJ 1042: [HAOI2008]硬幣購物 DP,與處理,容斥

just_sort發表於2017-01-20

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
Sample Output
4

27
解題方法: 容斥好題,首先不考慮數量限制,dp[i][j]表示點 i 種硬幣組成價值為 j 的物品的方案數,則有 dp[0][0]=0,dp[i][j]=dp[i
-1][j]+dp[i][j-c[i],最終答案就是 dp[4][n]。我們考慮個數限制,則答案應為任何一種都無限制的方案,減去
第一種硬幣個數超了的方案,減去第二種硬幣個數超了的方案,減去第三種硬幣個數超了的方案,減去第四種硬
幣個數超了的方案,加上第一種,第二種硬幣個數超了的方案,……,加上第一種,第二種,第三種,第四種硬
幣個數超了的方案,總之就是個容斥原理。我們考慮如何計算第一種硬幣個數超了的方案,設面值為 c,數量為
d,則我們必須要用第一種硬幣選出(d+1)×c 的價值,然後剩下的 n-(d+1)×c 隨便選,因此第一種硬幣個數超
了的方案就是 dp[4][n-(d+1)×c],剩下情況類似,這樣每次詢問的複雜度就做到了 O(1)。

程式碼如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int S = 100010;
#define rep(i, a, b) for(int i = a; i <= b; i++)
int c[5], d[5];
int n;
LL f[S], ans;
void dfs(int x, int k, int sum) //
{
    if(sum < 0) return ;
    if(x == 5){
        if(k & 1) ans -= f[sum];
        else ans += f[sum];
        return ;
    }
    dfs(x + 1, k + 1, sum - (d[x] + 1) * c[x]);
    dfs(x + 1, k, sum);
}
int main(){
    scanf("%d%d%d%d%d", &c[1], &c[2], &c[3], &c[4], &n);
    f[0] = 1;
    rep(i, 1, 4){
        rep(j, c[i], S - 1){
            f[j] += f[j - c[i]];
        }
    }
    rep(i, 1, n){
        int x;
        scanf("%d%d%d%d", &d[1], &d[2], &d[3], &d[4]);
        scanf("%d", &x);
        ans = 0;
        dfs(1, 0, x);
        printf("%lld\n", ans);
    }
    return 0;
}

相關文章