Java教程

卡牌

本文主要是介绍卡牌,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

题意:

现在有 \(m\) 轮游戏,第 \(i\) 轮游戏会给出 \(c_i\) 个质数,小 A 需要选择任意多张卡牌,使得这些卡牌上面的正整数的乘积能被该轮游戏给出的每个质数整除。

问有多少种卡牌的选法。

暴力容斥

考虑 \(s_i \leq 30\) 时的做法。

目标:使每个质数被整除的方案数,求所有属性集合的交集。

属性:满足是第 \(i\) 个质数的倍数的方案数。

求属性的交集,等于 全集 - 属性补集的并集

要求不是 \(S\) 中任意一个质数的数的个数 \(c\),那么方案数就是 \(2^c\)。

直接容斥:

\[\text{ans} = |U| - |\bigcup_{S \subset U, S \neq \emptyset} S| \]

\[= |U| - \sum_{S \subset U, S \neq \emptyset} (-1)^{|S|}2^{|S|} \]

质数性质

发现 \(s > 41\) 后的质因数只会出现一次,对于同一个 \(> 41\)的 质因数的倍数可以分成一个集合,记个数为 \(c\)。

如果给出了这个质因数,那么最终答案乘 \(2^{c} - 1\),表示必选至少一个这样的数。

如果没有给出,就乘 \(2^{c} - 1\)。

考虑到实际上,每个这样的数还有 \(\leq 41\) 的质因子,会对选择前 \(13\) 个质数产生影响。

在容斥的时候, 假设当前要求的属性集合 \(S\), 求满足不是集合中任意一个质数的倍数的方案数,求出了有 \(c\) 个数。

设 \(>41\) 的某个质数及其倍数中满足的有 \(d\) 个,假如它被强制选了,那么最后的方案数就是 \(2^{c - d}(2^d - 1)\)。

否则就是 \(2^{c - d}2^d = 2^c\)。

只要快速求出这 \(>41\) 的质数及其倍数在满足某些条件下的个数就能算这个东西。

于是使用状压 dp。

\(f_{s, i}\), 表示满足不是集合 \(s\) 中任意一个质数的倍数,是质数 \(i\) 或其倍数的个数。

算出这个就能直接按照上面的容斥算出方案数。

代码

#include<bits/stdc++.h>
using namespace std;
 
using ll = long long;
using ull = unsigned long long; 
const int MAXN = 2010;
template <typename T>
void Read(T &x) {
	x = 0; T f = 1; char a = getchar();
	for(; a < '0' || '9' < a; a = getchar()) if (a == '-') f = -f;
	for(; '0' <= a && a <= '9'; a = getchar()) x = (x * 10) + (a ^ 48);
	x *= f;
}	

const int mod = 998244353; 
inline int add(const int &a, const int &b) {
	static int c; 
	c = a + b;
	if (c >= mod) c -= mod;
	return c; 
} 
int dec(int a, int b) {
	int c = a - b;
	if (c < 0) c += mod;
	return c; 
}
inline int mul(const int &a, const int &b) {
	return 1ll * a * b % mod; 
}
int qpow(int a, int b) {
	int sum = 1;
	while (b) {
		if (b & 1) sum = mul(sum, a);
		a = mul(a, a);
		b >>= 1;
	}
	return sum; 
}

int len; 
int id[MAXN];
vector<int> d[MAXN];
int prime[MAXN], s[MAXN];  
bool p[MAXN]; 
int f[(1 << 13) + 10][MAXN]; 
int cnt[(1 << 13) + 10], tot[MAXN]; 

void init() {
	for (int i = 2; i <= 2000; i ++) {
		if (!p[i]) {
			prime[++ len] = i; 
			id[i] = len - 1; 
		}
		for (int j = i * 2; j <= 2000; j += i)
			p[j] = 1; 
	}
	for (int i = 2; i <= 2000; i ++)
		for (int j = 1; j <= len; j ++)
			if (i % prime[j] == 0) {
				if (j <= 13) s[i] |= 1 << (j - 1); 
				d[i].emplace_back(prime[j]); 
			}
}

int main() {

	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0); 
	init(); 
	int n, m; 
	cin >> n;
	for (int i = 1; i <= n; i ++) {
		int x; 
		cin >> x; 
		tot[x] ++; 
	}
	
	for (int i = 0; i < (1 << 13); i ++) {
		for (int j = 2; j <= 2000; j ++) {
			if (!(s[j] & i)) {
				f[i][d[j].back()] = add(f[i][d[j].back()], tot[j]); 
				cnt[i] = add(cnt[i], tot[j]); 
			}
		}
	}
	
	cin >> m;
	while (m --) {
		int len; 
		cin >> len;
		vector<int> b(len);
		int S = 0; 
		for (auto &i : b) {
			cin >> i; 
			if (i <= 41) 
				S |= 1 << id[i]; 
		}
		int ans = 0; 
		for (int i = 0; i < (1 << 13); i ++) {
			if ((i | S) != S) continue; 
			int prod = 1, ct = cnt[i];
			for (auto j : b) {
				if (j <= 41) continue; 
				prod = mul(prod, qpow(2, f[i][j]) - 1); 
				ct -= f[i][j];
			}
			prod = mul(prod, qpow(2, ct)); 
			ans = add(ans, mul(((__builtin_popcount(i) & 1)? mod - 1 : 1), prod)); 
		}
		cout << ans << endl; 
	}
	return 0; 
}

这篇关于卡牌的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!