2022省选Day2T1解题报告

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\
\___/   /
   \   /
    \ /*
—————\ *
      \*____
      |*    \
      |*
      \*
       *
       *
*/
posted @ 2022-04-26 17:48  xzj213  阅读(34)  评论(0编辑  收藏  举报