Loading

洛谷-P1450 硬币购物

P1450 硬币购物

容斥 || \(dp\) + 单调队列优化

容易看出是个多重背包,然后拿单调队列优化一下后,计算量为 \(O(4ns)\)

这种做法的话就是单调队列优化板子题

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
ll dp[5][maxn];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int c[5], d[5];
    for(int i=1; i<=4; i++) cin >> c[i];
    int t;
    cin >> t;
    dp[0][0] = 1;
    while(t--)
    {
        for(int i=1; i<=4; i++) cin >> d[i];
        int s;
        cin >> s;
        for(int i=1; i<=4; i++)
        {
            for(int j=0; j<c[i]; j++) dp[i][j] = dp[i - 1][j];
            for(int j=c[i]; j<c[i]+c[i]; j++)
            {
                int l = j - c[i], r = j - c[i];
                ll sum = dp[i - 1][j - c[i]];
                for(int k=j; k<=s; k+=c[i])
                {
                    r = k;
                    if((r - l) / c[i] > d[i])
                    {
                        sum -= dp[i - 1][l];
                        l += c[i];
                    }
                    sum += dp[i - 1][k];
                    dp[i][k] = sum;
                }
            }
        }
        cout << dp[4][s] << "\n";
    }
    return 0;
}

容斥的做法:

这个问题可以抽象成为一个带有限制的方程求方案数

\[\sum_{i=1}^{4} (c_i \times cnt_i) = s, cnt_i \le d_i \]

考虑容斥的话,方案数为:所有方案数 - 所有非法方案数

所有非法方案数可以通过枚举 \(cnt_i > d_i\) 的情况容斥求出

根据容斥定理,非法的方案数应该是 奇数个 \(cnt_i\) 非法的情况 - 偶数个 \(cnt_i\) 非法的情况

可以先做一个完全背包 \(dp[j] = dp[j] + dp[j - c_i]\)

那么 \(dp[x]\) 的意义就为,用 \(4\) 种硬币在无限制的条件下凑齐 \(x\) 块钱所有的方案数

因此在某个非法限制下的方案数就可以直接得到,例如在第 \(1\) 种硬币中,\(cnt_1 > d_1\),其他硬币数量不做约束的情况下,方案数为 \(dp[s - (d_1 + 1) \times c_1]\)

二进制枚举,然后容斥即可

计算量为:\(O(4s_{max} + n * (4 * 2^4))\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
ll dp[maxn];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int c[4], d[4];
    dp[0] = 1;
    for(int i=0; i<4; i++)
    {
        cin >> c[i];
        for(int j=c[i]; j<maxn; j++)
            dp[j] += dp[j - c[i]];
    }
    int t;
    cin >> t;
    while(t--)
    {
        for(int i=0; i<4; i++) cin >> d[i];
        int s;
        cin >> s;
        ll ans = 0;
        for(int i=0; i<=15; i++)
        {
            int cnt = 0;
            ll sum = 0;
            for(int j=0; j<4; j++)
            {
                if(i >> j & 1)
                {
                    cnt++;
                    sum += (d[j] + 1) * c[j];
                }
            }
            if(sum > s) sum = 0;
            else sum = dp[s - sum];
            if(cnt & 1) sum = -sum;
            ans += sum;
        }
        cout << ans << "\n";
    }
    // cout << endl;
    return 0;
}
posted @ 2022-12-16 17:21  dgsvygd  阅读(48)  评论(0编辑  收藏  举报