题意
给出一个长度为 \(4\) 的序列 \(c\),给出 \(n\) 个询问,每个询问给出一个长度为 \(4\) 的序列 \(d\) 和 整数 \(s\),要求构造出长度为 \(4\) 的序列 \(t\),使得 \(s = \sum_{i=1}^4 c_i \cdot t_i\),且 \(\forall (x \in \R) \land (1 \le x \le 4), t_x \le d_x)\),求 \(t\) 的方案数
sol
一眼多重背包问题,但无论使用哪种做法都无法在 \(1s\) 内解决,只好思考容斥。
(注:接下来,我们记 \(cnt_x\) 表示满足 \(x = \sum_{i=1}^4 c_i \cdot t_i\)的方案数)
答案显然不是 \(cnt_s\),因为存在序列 \(d\) 限制。考虑减掉对于 \(1\sim 4\) 中的每个数 \(i\),当 \(t_i > d_i\) 时的方案数,即 \(cnt_{s - c_i \cdot (d_i + 1)}\)(因为需要保证 \(t_i > d_i\),因此先选出 \(d_i + 1\) 个 \(c_i\),再正常选择)。
此时多减了两个数多选的情况,由此,即可写出容斥式
\(cnt\) 数组也很好计算,事实上,它就是完全背包一维优化的 \(f\) 数组。
代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 100005;
int c[4], d[4];
int n, s;
long long f[N];
int p(int x){
return 1 - x % 2 * 2;
}
void init(){
f[0] = 1;
for (int i = 0; i < 4; i ++ )
for (int j = c[i]; j <= 100000; j ++ )
f[j] += f[j - c[i]];
}
int main(){
scanf("%d%d%d%d%d", &c[0], &c[1], &c[2], &c[3], &n);
init();
while (n -- ){
scanf("%d%d%d%d%d", &d[0], &d[1], &d[2], &d[3], &s);
long long ans = 0;
for (int s0 = 0; s0 < 16; s0 ++ ){
int l = 0;
long long sum = 0;
for (int u = 0; u < 4; u ++ )
if ((s0 >> u) & 1) l ++ , sum += (long long) c[u] * (d[u] + 1);
if (s >= sum) ans += p(l) * f[s - sum];
}
printf("%lld\n", ans);
}
}
蒟蒻犯的若至错误
- init() 和 main() 中的下标起始位置不统一