牛客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;
}
posted @ 2019-10-06 08:01  四季夏目天下第一  阅读(102)  评论(1编辑  收藏  举报