牛客52 烹饪
题意
有\(N\)种食材,每种食材有无限个,并且每种食材都有一个美味值,记为\(a_i\)
请你选出若干个食材,使这些食材相互组合可以烹饪出任意正整数美味值的菜肴
菜肴的烹饪过程如下:
- 初始时菜肴的美味值为\(0\)
- 每次加入一种食材,可以选择让菜肴的美味值上升\(a_i\)或下降\(a_i\)
请求出符合条件的食材挑选方案个数,一个食材方案个数是一个不可重无序集合\((b_1,b_2...b_m)\)表示从原来的\(n\)种食材中挑选了\(m\)种食材,并且可以用这\(m\)种食材烹饪出任意美味度的菜肴
取模\(998244353\)
\(N\leq 3000,a_i\leq 2000\)
解法
观察到题目给出的条件很弱,考虑把条件强化为好求出的限制
很容易发现的是,能表示出任意正整数 \(\leftrightarrow\) 能表示出1
而什么样的数列集合能表示出\(1\)呢?
从简单的开始考虑,若只有两个数\(u,v\),它能表示出\(1\)的充要条件是\((u,v)=1\)
由于\(u,v\)互质,那么\(u,v\)由\(u\)的整数倍形成的集合模\(v\)的剩余系中一定有\(1\),也就一定能表示出\(1\)
推广到长度为\(n\)的情况,我们能发现,如果\((a_1,a_2...a_n)=1\),那么这个序列就是合法的
这样我们又把能表示出1这一条件转化为了最大公因数为1这一更加强的条件
我们的任务实际上转化为了求\(gcd=1\)的子序列个数
这个用DP和容斥都可以求,这里写一下DP做法
设\(f[i][j]\)为前\(i\)个数中\(gcd=j\)的子序列个数,转移时讨论\(a_j\)加入或不加入就可以了,最后的答案即为\(f[N][1]\)
代码
#include <cstdio>
using namespace std;
const int MAX_N = 3010;
const int mod = 998244353;
int N;
int a[MAX_N], f[MAX_N][MAX_N];
inline void add(int& x, int y) { x = (x + y) % mod; }
inline int gcd(int x, int y) { return !y ? x : gcd(y, x % y); }
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) scanf("%d", a + i);
for (int i = 1; i <= N; ++i) {
f[i][a[i]] = 1;
for (int j = 1; j <= 2000; ++j) {
add(f[i][j], f[i - 1][j]);
add(f[i][gcd(j, a[i])], f[i - 1][j]);
}
}
printf("%d\n", f[N][1]);
return 0;
}