洛谷-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;
}