P1450 [HAOI2008] 硬币购物 题解

发布时间 2023-09-01 22:01:01作者: MoyouSayuki

P1450 [HAOI2008] 硬币购物 题解

首先考虑只有一种硬币的情况。

如果取的数量没有限制,就是一个完全背包,\(f_i\) 表示背包体积为 \(i\) 的选择方案数,显然 \(f_j = f_{j - v}\)

如果取的数量有限制,用多重背包做一遍会超时,考虑以下思路:所有方案数 - 不合法方案数 = 合法方案数。

所有的方案数显然就是 \(f_s\),而不合法的方案数就是 \(f_{s- c\times(d+1)}\),原因如下:

最多选择 \(d\) 次硬币,如果选择了 \(d + 1\) 个硬币就是不合法的情况,所以我们先选 \(d + 1\) 个硬币出来,剩下的体积随便放,都是不合法的情况 ,而这 \(d + 1\) 个硬币的体积占用是 \(c\times (d + 1)\)。剩下的体积就是 \(f_{s - c\times(d + 1)}\)

所以合法的方案数就是 \(f_s - f_{s-c\times(d + 1)}\)

考虑有多种硬币的情况,如果有很多种硬币,答案不能简单的相加,因为这样会算重复,比如算硬币 \(1\) 不合法的情况中已经包含了硬币 \(1\) 和硬币 \(2\) 都不合法的情况,如果再直接减去硬币 \(2\) 不合法的情况,硬币 \(1\) 和硬币 \(2\) 都不合法的情况相当于被减去了两次,所以需要再加上硬币 \(1\) 和硬币 \(2\) 都不合法的情况 \(f_{s - c_1\times(d_1+1)-c_2\times(d_2+1)}\)

所以考虑容斥原理做一下就完美去重了。

时间复杂度:\(O(s+n2^V)\)\(V\) 是硬币种数,此题为 \(4\)

// Problem: P1450 [HAOI2008] 硬币购物
// Contest: Luogu
// Author: Moyou
// Copyright (c) 2023 Moyou All rights reserved.
// Date: 2023-09-01 19:32:55

#include <algorithm>
#include <iostream>
#define int long long
using namespace std;
const int N = 1e5 + 10;
int n, v[5], w[5], dp[N];

signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> v[1] >> v[2] >> v[3] >> v[4] >> n;
    dp[0] = 1;
    for(int i = 1; i <= 4; i ++) 
        for(int j = v[i]; j <= N - 10; j ++) dp[j] += dp[j - v[i]];
    for(int i = 1, s, ans, sum; i <= n; i ++) {
        cin >> w[1] >> w[2] >> w[3] >> w[4] >> s;
        ans = 0;
        for(int i = 0; i < (1 << 4); i ++) {
            sum = s;
            for(int j = 0; j < 4; j ++) 
                if((i & (1 << j))) sum -= (w[j + 1] + 1) * v[j + 1];
            if(sum >= 0) ans += ((__builtin_popcount(i) & 1) ? -1 : 1) * dp[sum]; 
        }
        cout << ans << '\n';
    }
    return 0;
}