P8292 [省选联考 2022] 卡牌(官方数据) 题解

shadowice1984

2022-04-22 01:34:33

Solution

~~时间复杂度理论都白学了系列~~ 可能随着评测机越来越快$O(nm)$能通过的数据极限也在增长吧…… 前几年觉得5000×5000就有点悬了现在8000×18000也变成随便过了…… ____________________________ ### 前置芝士:容斥原理 如果不会容斥原理的话可以做一做[这道题](https://www.luogu.com.cn/problem/P3349)学习一下。 这道题用的是最经典的容斥原理:如果我们想算合法方案总数,其中每一个合法方案需要满足n条限制。那么我们就先算出随便取的方案数(打破0条限制),然后减去打破一条限制的方案数,加上打破两条限制的方案数……这样把打破0~n条限制的方案数放在一起加加减减就能得到总方案数。 ## 本题题解 ### 从朴素的容斥开始 整理一下题意就是:给定$n$张写着数字的卡片,多次询问,每次询问给出一个大小为$c$的质数的集合$T=\{p_{1},p_{2} \dots p_{c}\}$,求有多少种选取卡片的方法,使得每一个在$T$中出现的质数$p_{i}$都是被选中的卡片上数字之乘积$Q$的约数。 相当于有$c$条限制,第$i$条限制就是“选择的数字中至少有一个是$p_{i}$的倍数”。打破这条限制就变成了“不能选择$p_{i}$的倍数”。 那么我们就可以根据容斥原理列出一个朴素的式子: $$Ans=\sum_{P \subseteq T }(-1)^{|P|}2^{cnt(P)}$$ 其中$cnt(P)$表示上面的数字不是$P$中任意一个质数的倍数的卡片张数。 这个式子的意义就是先枚举要打破的限制集合$P$。 为了打破集合中的限制我们就只能在$cnt(P)$张卡片中自由选择,所以方案数就是$2^{cnt(p)}$。 $(-1)^{|P|}$则是容斥系数。 ### 分类讨论 不过直接这么做复杂度是指数级别的,肯定过不去,所以我们还需要找点别的性质。 注意到数字的值域很小,只有$2000$。 通过打表我们发现$43×47=2021,43×41=1763$,这说明一个数字**不可能有两个不同的大于等于$43$的质因子**。 $2000$以内的质数有$303$个,而小于$43$的质数只有$13$个。 $13$的确是个令人心动的小数字,我们可以围绕它搞各种各样的暴力,比如状压。 于是我们将质数分为两个集合$A,B$。集合$A$中是大于等于$43$的质数,而集合$B$中则是小于$43$的质数。 设$T_{A}=T \cap A,T_{B}=T \cap B$,重新写一次之前的容斥式子: $$Ans=\sum_{P_{B}\subseteq T_{B}}\sum_{P_{A} \subseteq T_{A}}(-1)^{|P_{B} \cup P_{A}|}2^{cnt(P_{B} \cup P_{A})}$$ 做一点小小的整理: $$Ans=\sum_{P_{B}\subseteq T_{B}}\sum_{P_{A} \subseteq T_{A}}(-1)^{|P_{B}| + |P_{A}|}2^{cnt(P_{B} \cup P_{A})}$$ $$Ans=\sum_{P_{B}\subseteq T_{B}}(-1)^{|P_{B|}}\sum_{P_{A} \subseteq T_{A}}(-1)^{|P_{A}|}2^{cnt(P_{B} \cup P_{A})}$$ ### 计算$cnt(P_{B} \cup P_{A})$ 那么现在我们需要计算$cnt(P_{B} \cup P_{A})$,有没有什么简单的方法计算它呢? 不要忘了每个数字最多含有一个$A$中的质因子,由此我们可以通过枚举A中的质因子$i$来计算$cnt(P_{B} \cup P_{A})$: $$cnt(P_{B} \cup P_{A})=\sum_{i \in A,i \notin P_{A}}newcnt( P_{B},i)$$ 其中$newcnt(P_{B},i)$表示符合以下条件的卡牌张数: 1.卡牌上的数字在$A$中的质因子恰好为$i$。 2.这个数字不是$P_{B}$中任何一个质数的倍数。 不过上面的式子有一点小锅,就是没有处理这个数字没有在$A$中的质因子的情况。 解决方法也很简单,把这个数字的$i$值钦定为$0$,再强行把$0$塞到$A$里去,我们的式子就又成立了。 $newcnt(P_{B},i)$的总状态量只有$2^{13}×290$,因此可以在$O(2^{13}\max{S_{i}})$的时间内预处理出来。 ### 推点式子 好了,现在我们把$cnt(P_{B} \cup P_{A})$代入到之前的式子里: $$Ans=\sum_{P_{B}\subseteq T_{B}}(-1)^{|P_{B|}}\sum_{P_{A} \subseteq T_{A}}(-1)^{|P_{A}|}2^{\sum_{i \in A,i \notin P_{A}}newcnt( P_{B},i)}$$ 把$\Sigma$从指数上拆下来变成$\Pi$: $$Ans=\sum_{P_{B}\subseteq T_{B}}(-1)^{|P_{B|}}\sum_{P_{A} \subseteq T_{A}}(-1)^{|P_{A}|}\prod_{i \in A,i \notin P_{A}}2^{newcnt( P_{B},i)}$$ 式子的后半部分还可以写成这样: $$\sum_{P_{A} \subseteq T_{A}}\prod_{i \in P_{A}}(-1)\prod_{i \in A,i \notin P_{A}}2^{newcnt( P_{B},i)}$$ 那其实就是这个式子的展开式: $$\prod_{i \in A}(2^{newcnt( P_{B},i)}-[i \in T_{A}])$$ 其中$[i \in T_{A}]$的值是1当且仅当$i \in T_{A}$,其他情况值为0。 解释一下这两个式子为什么相等:类似于二项式定理的证明,最后一个式子里面每个括号可以选$2^{newcnt( P_{B},i)}$或者$-1$。 选前者就认为这个元素不在$P_{A}$中,选后者就认为在$P_{A}$中,那么这些括号相乘的所有展开项就自动枚举了所有的$P_{A}$。由于不在$T_{A}$中的元素一定不在$P_{A}$中,所以需要用$[i \in T_{A}]$来限制范围。 那么现在我们的式子变成了这样: $$Ans=\sum_{P_{B}\subseteq T_{B}}(-1)^{|P_{B|}}\prod_{i \in A}(2^{newcnt( P_{B},i)}-[i \in T_{A}])$$ 提一个公因式: $$Ans=\sum_{P_{B}\subseteq T_{B}}(-1)^{|P_{B|}}\prod_{i \in A}2^{newcnt( P_{B},i)}\prod_{i \in T_{A}}(1-2^{-newcnt(P_{B},i)})$$ 简单分析一下暴力算上面式子的复杂度。 $1-2^{-newcnt(P_{B},i)}$和$\prod_{i \in A}2^{newcnt( P_{B},i)}$都能在$O(2^{13}\max{S_{i}})$的时间内预处理,复杂度不是问题。 预处理之后回答一次询问的复杂度是$O(2^{|T_{B}|}|T_{A}|)$的。 所以回答的总复杂度就是$O(2^{13}\Sigma c_{i})$的。 总复杂度高达$8192×18000$,我也不知道是评测机速度提起来了还是没跑满,总之直接过去了……。 ~~算了,复杂度就图一乐,过题还得看信仰。~~ 上代码~ ```cpp #include<cstdio> #include<algorithm> #include<set> using namespace std; const int N=2010; typedef long long ll; const ll mod=998244353; const int MXB=4096*2; ll po(ll a,ll p)//快速幂 { ll r=1; for(;p;p>>=1,a=a*a%mod) if(p&1)r=r*a%mod; return r; } ll mi2[9000]; int siz[9000]; ll f[9000][N];//1 -2^(-newcnt) ll mul[9000]; ll cnta[N]; int pb[N]; int pa[N]; int mindiv[N]; bool ta[N]; int q[N];int hd; //调试用的宏 # define prita(a,n)\ printf(#a":");for(int i=0;i<=n;i++)printf("%lld ",a[i]);printf("\n"); int main() { siz[0]=0; for(int i=1;i<=MXB;i++)siz[i]=siz[i>>1]^((i&1)); mi2[0]=1; for(int i=1;i<N-5;i++)mi2[i]=mi2[i-1]*2%mod; for(int i=1;i<N-5;i++)mindiv[i]=i; for(int i=2;i<N-5;i++) for(int j=i+i;j<N-5;j+=i) //处理每个数字的最小约数(也是最小质因子) mindiv[j]=min(mindiv[j],i); int tot=0; //处理出每个数字的A集合和B集合 for(int i=2;i<N-5;i++) { if(mindiv[i]==i)//质数的情况 { if(i<43) { pb[i]=1<<tot,tot++; } else { for(int j=i;j<N-5;j+=i) pa[j]=i; } } else //非质数的情况除掉最小质因子递归 { pb[i]=pb[i/mindiv[i]]|pb[mindiv[i]]; } } int n; scanf("%d",&n); int mxs=0; for(int i=1;i<=n;i++) { int s;scanf("%d",&s); cnta[s]++; mxs=max(mxs,s); } for(int s=0;s<MXB;s++)mul[s]=1; for(int i=1;i<=mxs;i++) { for(int s=MXB-1;s>=0;s--) if((s&pb[i])==0)//计算newcnt { f[s][pa[i]]+=cnta[i]; } } for(int i=0;i<=mxs;i++) if(i==pa[i])//根据newcnt计算需要预处理的两个数组。 { for(int s=0;s<MXB;s++) { //这里f的定义发生了改变,从newcnt变成了1-2^(-newcnt) f[s][i]=po(2,f[s][i])%mod; (mul[s]*=f[s][i])%=mod; f[s][i]=(1+mod-po(f[s][i],mod-2))%mod; } } int m; scanf("%d",&m); for(int i=1;i<=m;i++) { int c; scanf("%d",&c); int tb=0; hd=0; for(int j=1;j<=c;j++) { int p; scanf("%d",&p); if(p<43) { tb|=pb[p]; } else { if(ta[p]==false)q[++hd]=p; ta[p]=true; } } for(int i=1;i<=hd;i++)ta[q[i]]=false; //计算tb,ta,ta存到了数组q里。 ll ans=0; for(int sb=tb;1;sb=(sb-1)&tb)//枚举子集根据式子计算 { ll ret; if(siz[sb])ret=mod-mul[sb]; else ret=mul[sb]; for(int j=1;j<=hd;j++) (ret*=f[sb][q[j]])%=mod; (ans+=ret)%=mod; if(sb==0)break; } printf("%lld\n",ans); } return 0;//拜拜程序~ } ```