1. 程式人生 > >P1450 [HAOI2008]硬幣購物 - 容斥 - DP

P1450 [HAOI2008]硬幣購物 - 容斥 - DP

直接做多重揹包複雜度太高了,我們想想是什麼因素限制瞭如此高的複雜度
如果沒有硬幣個數的限制呢?直接用完全揹包預處理後查詢就好了
那麼現在考慮一個較為簡單的問題,只有一種硬幣有限制
設dp[s]為買了價值為s時的方案數,先暫時當做完全揹包預處理出dp陣列
若那個唯一被限制硬幣的面值為c,個數為d,那麼真正的方案數(其他硬幣都是完全揹包)是dp[s] - dp[s - c * (d + 1)]
這個式子有點不顯然,因為這個式子很巧妙。考慮多少方案是多餘的,是拿了d個硬幣以上的方案吧,那麼至少也得拿d + 1個硬幣的情況下,你會發現dp[s - c * (d + 1)]中所有的方案,都可以通過加上d + 1個硬幣c,拼接出

最終總價值s,這樣的話就有dp[s - c * (d + 1)]個方案是多餘的,並且所有大於s - c * (d + 1)的狀態是不可能加上多餘d個硬幣c轉移到s狀態的,注意這是方案數DP,和最優性DP不一樣
然後就可以容斥搞一下,我們算“多餘的方案數”,設其值為x,先把只考慮某一種硬幣限制的多餘方案數都加到x裡面,然後這樣肯定會加重複的,減去既不滿足1的又不滿足2的方案數,然後再加回來同時不滿足123的方案數,然後用總方案數減去x就好了
如果容斥式子不好寫可以兩個方向各思考一次:既滿足1要求又滿足2要求 或 既不滿足1又不滿足2的,這道題用不滿足比較好寫,因為知道了目前多餘的方案數有多少
既不滿足1要求又不滿足2要求的:dp[s - c1 * (d1 + 1) - c2 * (d2 + 1)]
這個顯然,不滿足的比滿足的方案好想,兩個限制同時不滿足只能是上式

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <cmath>
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl;
const int MAXN = 100000 + 10;
const int INF = 1 << 30;
typedef long long ll;
int c[5],d[5],s,tot;
ll f[MAXN];
void init() {
    f[0] = 1;
    for(int i=1; i<=4; i++) {
        for(int j=0; j<=100000; j++) {
            if(j >= c[i])
                f[j] += f[j - c[i]];
        }
    }
}
int main() {
    cin >> c[1] >> c[2] >> c[3] >> c[4] >> tot;
    init(); 
    for(int i=1; i<=tot; i++) {
        cin >> d[1] >> d[2] >> d[3] >> d[4] >> s;
        ll ans = f[s];
        for(int i=1; i<(1<<4); i++) {
            int now = s;
            int x = i, tot = 0; 
            for(int j=1; j<=4; j++)
                if(x & (1 << (j - 1))) now -= c[j] * (d[j] + 1), tot++;
            if(now < 0) continue;
            if(tot % 2) ans -= f[now];
            else ans += f[now];
        }
        printf("%lld\n", ans);
    }
    return 0;
}