牛客52 烹饪

题意

(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;
}
原文地址:https://www.cnblogs.com/VeniVidiVici/p/11626374.html