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)] ,也就是说硬币1和硬币2分别拿了超过d1和超过d2个,注意这里的dp数组在数值上是等于多余方案数的,而其实这里的dp数组记录的也仅仅是数值大小(巧妙应用dp数组)
这个显然,不满足的比满足的方案好想,两个限制同时不满足只能是上式

#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;
}
posted @ 2018-11-02 07:29  Zolrk  阅读(116)  评论(2编辑  收藏  举报