P1450 (容斥 + DP)

题意:

共有 4 种硬币。面值分别为 c1,c2,c3,c4。
某人去商店买东西,去了n 次,对于每次购买,他带了 di 枚种硬币,想购买 s 的价值的东西。请问每次有多少种付款方法。

思路:

看起来像是一个多重背包求方案数的模板题,但是由于有 n 组,所以每次都求一次多重背包绝对会超时。
显然多重背包即有限制的方案数,直接计算有限制的方案数比较难,考虑正难则反:
有限制的方案数 = 无限的方案数 - 超过限制方案数
首先无限制的方案数显然就是完全背包,我们可以先用完全背包预处理出所有的方案数,即 f [ i ] 表示购买价值为 i 的方案数。
考虑减去超过显示的即不合法的部分,考虑使用容斥原理。
首先考虑一种硬币超额使用的方案数怎么计算。若第 i 种硬币超额使用,即超过了原定的 \(d_j\) 个硬币的限制,我们可以先强制选了\(d_j + 1\)个第 j 种硬币,这样剩下的价值里,4 种硬币随便选,这样就能保证第 j 种硬币一定超额使用,第 j 种硬币超额的不合法的方案数就等于\(f[s - (d_j + 1) * c_j ]\)
然后我们只需要使用容斥原理奇加偶减即可。加上一种硬币不合法的方案数,减去两种硬币不合法的方案数,加上三种硬币不合法的方案数。二进制枚举子集即可。

AC代码:

// -----------------
//#pragma GCC optimize(2)
#include <iostream>
#include <cstring>
#include <algorithm>
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(0);
#define endl '\n'
#define rep(i,x,y) for(int i = (x); i <= (y); i ++)
#define dec(i,y,x) for(int i = (y); i >= (x); i --)
#define int long long 
using namespace std;

const int N = 1e5 + 7;
int f[N],c[100],d[100];
int s;

void solve()
{
    rep(i,1,4) cin >> d[i];
    cin >> s;
    int ans = f[s], now;
    for(int i = 1; i < (1 << 4); i ++)
    {
        now = s;
        int ff = 0;
        for(int tmp = i,j = 1; tmp; tmp >>= 1,j ++)
        {
            if(tmp & 1)
            {
                ff ^= 1;
                now -= (d[j] + 1) * c[j];
            }
        }
        if(now >= 0)
        {
            if(ff) ans -= f[now];
            else ans += f[now];
        }
    }
    cout << ans << endl;
}

signed main()
{
    IOS 
    int T = 1;
    rep(i,1,4) cin >> c[i];
    cin >> T;
    f[0] = 1;
    rep(i,1,4)
        rep(j,c[i],N-8)
            f[j] += f[j - c[i]];
            
    while(T --) { solve(); }
    return 0;
}
posted @ 2023-07-11 16:10  陌上&初安  阅读(40)  评论(0)    收藏  举报