[HAOI2008] 硬币购物

[HAOI2008] 硬币购物

题目描述

共有 \(4\) 种硬币。面值分别为 \(c_1,c_2,c_3,c_4\)

某人去商店买东西,去了 \(n\) 次,对于每次购买,他带了 \(d_i\)\(i\) 种硬币,想购买 \(s\) 的价值的东西。请问每次有多少种付款方法。

输入格式

输入的第一行是五个整数,分别代表 \(c_1,c_2,c_3,c_4, n\)

接下来 \(n\) 行,每行有五个整数,描述一次购买,分别代表 \(d_1, d_2, d_3, d_4,s\)

输出格式

对于每次购买,输出一行一个整数代表答案。

样例 #1

样例输入 #1

1 2 5 10 2
3 2 3 1 10
1000 2 2 2 900

样例输出 #1

4
27

提示

数据规模与约定

  • 对于 \(100\%\) 的数据,保证 \(1 \leq c_i, d_i, s \leq 10^5\)\(1 \leq n \leq 1000\)

[HAOI2008] 硬币购物

分析

第一眼看到题目,许多硬币组成一个价值的方案,限制个数,指定花费,这不就是多重背包求方案数吗?

然后注意到多次询问,每次都要做多重背包,就算是单调队列优化,复杂度也是 \(O(4ns)\)。显然出题人是专门卡多重背包的,多重背包的思路一定是没前途的。这样就要换一种思路。

\[完全背包+容斥 \]

先分别考虑每种硬币,有限制不好搞,可以考虑用全部状态减去非法状态。

\(f_j\) 表示每个硬币可用无数次,得到价值为 \(j\) 的方案数。显然状态转移就是 \(f_j=f_j+f_{j-c}\)

非法状态就是硬币至少用了 \(d+1\) 个,将这 \(d+1\) 个硬币视为最后加上的,显然 \(f_j\) 就是由 \(f_{j-(d+1) \times c}\) 转移来的。

也就是说合法方案 \(ans=f_j-f_{j-(d+1) \times c }\)

同理,四种硬币也是总状态减去非法状态,不过四种硬币的非法情况的方案数会有重叠,这就要用容斥原理了。

设第 \(i\) 种硬币的非法情况的方案数为 \(A_i\)

\[\begin{aligned} & |A_1 \cup A_2 \cup A_3 \cup A_4| & \\ & =(|A_1|+|A_2|+|A_3|+|A_4|) &\\ & -(|A_1 \cap A_2|+|A_1 \cap A_3|+|A_1 \cap A_4|+|A_2 \cap A_3|+|A_2 \cap A_4|+|A_3 \cap A_4|) & \\ & +(|A_1 \cap A_2 \cap A_3|+|A_1 \cap A_2 \cap A_4|+|A_1 \cap A_3 \cap A_4|+|A_2 \cap A_3 \cap A_4|) & \\ & -|A_1 \cap A_2 \cap A_3 \cap A_4| & \end{aligned}\]

最后用 \(f_j\) 减去即可。

总之一句话 \(无限制-一个硬币的限制+两个硬币的限制-三个硬币的限制+四个硬币的限制\)

可以用壮压的方法枚举子集来进行容斥计算 \(state\) 的第 \(i\) 位为 \(1\),则该状态有第 \(i+1\) 种硬币的限制。

code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5,M=10;
int n,s;
int c[M],d[M];
ll f[N],ans;
int main ()
{
    for(int i=1;i<=4;i++) cin>>c[i];
    f[0]=1;
    for(int i=1;i<=4;i++)
        for(int j=c[i];j<N;j++)
            f[j]=f[j]+f[j-c[i]];
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        for(int i=1;i<=4;i++) cin>>d[i];
        cin>>s;
        ans=0;
        for(int i=0;i<(1<<4);i++)
        {
            int cnt=0;
            ll t=0;
            for(int j=1;j<=4;j++) if(i&(1<<(j-1))) cnt++,t+=c[j]*(d[j]+1);
            if(s>=t)
            {
                if(cnt&1) ans-=f[s-t];
                else ans+=f[s-t];
            }
        }
        cout<<ans<<"\n";
    }
    return 0;
}

部分语言参考:https://www.cnblogs.com/LLTYYC/p/10773808.html
https://www.luogu.com.cn/article/ywr15b0z

posted @ 2024-07-24 21:14  zhouruoheng  阅读(18)  评论(0编辑  收藏  举报