题意
有(N)种食材,每种食材有无限个,并且每种食材都有一个美味值,记为(a_i)
请你选出若干个食材,使这些食材相互组合可以烹饪出任意正整数美味值的菜肴
菜肴的烹饪过程如下:
- 初始时菜肴的美味值为(0)
- 每次加入一种食材,可以选择让菜肴的美味值上升(a_i)或下降(a_i)
请求出符合条件的食材挑选方案个数,一个食材方案个数是一个不可重无序集合((b_1,b_2...b_m))表示从原来的(n)种食材中挑选了(m)种食材,并且可以用这(m)种食材烹饪出任意美味度的菜肴
取模(998244353)
(Nleq 3000,a_ileq 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
", f[N][1]);
return 0;
}