2022省选Day2T1解题报告
题目描述
给定\(n\)个数\(a_i\),有\(m\)次询问,每次给定\(c_i\)个质数,你可以选出一些数满足选出的数的乘积可以被给定的\(c_i\)个质数整除,每次询问求有多少种选法
\(n\leq 1e6~~a_i\leq 2000~\sum c_i \leq 18000\)
题解
当我们拿到一个询问选法的题时,大概率不是数学就是\(dp\)
显然我们很容易看出需要容斥一手,但是发现如果直接容斥统计有点难
考虑正难则反,我们可以处理有多少种取法不满足条件
那么答案肯定就是\(2^n-res\) (\(res\)是不满足条件的选法数)
我们先考虑部分分怎么做
对于\(a_i<=30\)的情况,我们发现\(<30\)的质数只有10个
那么答案肯定就是\(2^n\)减去某一个质数不选的方案数,再加上某两个质数不选的方案数,再减去某三个质数不选的方案数……
然后考虑推广,我们发现对于一个数\(x\)最多只会有一个大于\(\sqrt x\)的质因数
所以我们可以把大于\(\sqrt{x}\)的数单独处理
而且对于两个大于\(\sqrt{x}\)的质数,是不可能同时不选也就是相对独立的
所以我们的容斥可以拿\(>43\)的质数必须有的方案数去减
这种做法可以当作套路去记一下,类似的题还有luogu2150 [NOI2015] 寿司晚宴
那么对于小于\(\sqrt{x}\)的质数总共最多\(13\)个(\(43*47>2000\))
所以我们考虑状压\(dp\)
我们可以求出\(f_{i,j}\)表示状态为\(i\),且当前枚举的\(>43\)的质数为\(j\)的集合里的元素个数(\(i\)状态记录有哪几个质数不选)
然后枚举\(>43\)的质数,把这些质数从集合中剔除,并将状态为\(i\)的答案乘上\(2^{cnt}-1\)(\(cnt\)指能被当前枚举的质数整除的数的个数)
然后对于状态\(i\)中\(1\)的奇偶性判断加或减就行了
代码
#include <ctime>
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
#define int long long
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
using namespace std;
const int maxn=1e6+5,Mod=998244353;
int n,a[maxn],m,cnt[2000+5],id[maxn],mp[2000+5];
int f[(1<<13)+5][350],cnt1[(1<<13)+5],ans;
bool is_prime[maxn];
vector<int>prime,s[2000+5];
void chkmax(int &x,int y) {if (x<y) x=y;}
void chkmin(int &x,int y) {if (x>y) x=y;}
int read() {
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {if (ch=='-') f=-1;ch=getchar();}
while(ch<='9' && ch>='0') {x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
int lowbit(int x) {return x&(-x);}
int ksm(int a,int b) {
int res=1;
while(b) {
if (b&1) (res*=a)%=Mod;
b>>=1;(a*=a)%=Mod;
}
return res;
}
void init() {
is_prime[1]=1;
for (int i=2;i<=2000;i++) {
if (!is_prime[i]) {
id[i]=prime.size();
prime.push_back(i);
}
for (size_t j=0;j<prime.size() && i*prime[j]<=2000;j++) {
is_prime[i*prime[j]]=1;
if (i%prime[j]==0) break;
}
}
for (int i=2;i<=2000;i++) {
for (int j=0;j<prime.size();j++) {
if (i%prime[j]) continue;
s[i].push_back(j);
if (j<13) mp[i]|=(1<<j);
}
}
}
int calc(int x) {
int res=0;
while(x) {
x-=lowbit(x);
res++;
}
return res;
}
signed main() {
file(luogu8292);
n=read();
for (int i=1;i<=n;i++) {
a[i]=read();
cnt[a[i]]++;
}
init();
for (int i=0;i<(1<<13);i++) {
for (int j=2;j<=2000;j++) {
if (i&mp[j]) continue;
f[i][s[j].back()]+=cnt[j];
cnt1[i]+=cnt[j];
}
}
int q=read();
while(q--) {
m=read();
int tmp=0;ans=0;
for (int i=1;i<=m;i++) {
a[i]=read();
if (id[a[i]]<13) tmp|=(1<<id[a[i]]);
}
// cout<<"tmp = "<<tmp<<endl;
sort(a+1,a+1+m);a[m+1]=-1;
for (int i=0;i<(1<<13);i++) {
if ((tmp|i)>tmp) continue;
int res=1,tot=cnt1[i];
for (int j=1;j<=m;j++) {
if (a[j]==a[j+1]) continue;
if (id[a[j]]<13) continue;
(res*=(ksm(2,f[i][id[a[j]]])-1+Mod)%Mod)%=Mod;
tot-=f[i][id[a[j]]];
}
(res*=ksm(2,tot))%=Mod;
if (calc(i)&1) ans=((ans-res)%Mod+Mod)%Mod;
else ans=(ans+res)%Mod;
// cout<<i<<' '<<tot<<endl;
}
cout<<ans<<endl;
}
return 0;
}
/*
---
/Y A\
\___/ /
\ /
\ /*
—————\ *
\*____
|* \
|*
\*
*
*
*/