【CF585E】Present for Vitalik the Philatelist(因数容斥套路题)
- 给定一个大小为\(n\)的可重集。
- 问有多少种方式从\(n\)中取出一个元素\(x\)和一个子集\(S\),满足\(x\not\in S\)且\(\gcd\{S\}>1,\gcd(\gcd\{S\},x)=1\)。
- \(n\le5\times10^5\),值域为\([2,10^7]\)。
因数容斥
应该算是一种做滥掉的套路了吧。。。
这个套路大致就是,令\(f(i)\)表示\(d\)是\(i\)的倍数的方案数,\(f'(i)\)表示\(d=i\)的方案数。
显然有\(f'(i)=f(i)-\sum_{i|j}f'(j)\),那么只要从大到小枚举\(i\)做一遍就好了,且这样子做一遍的复杂度应该是\(O(V\ln V)\)的。
容斥+推式子
首先,我们发现如果\(\gcd\{S\}=1\),则\(\gcd(\gcd\{S\},x)=1\)必然成立。
因此只需求出\(\gcd(\gcd\{S\},x)=1\)的方案数,然后减去\(\gcd\{S\}=1\)的方案数,就可以得到题目中要求的方案数了。
于是,我们设\(f(i)\)表示\(\gcd(\gcd\{S\},x)\)是\(i\)的倍数的方案数,\(g(i)\)表示\(\gcd\{S\}\)是\(i\)的倍数的方案数,并套路地定义\(f'(i)\)和\(g'(i)\)。
显然,答案就是\(f'(1)-g'(1)\)。那么现在的问题就是如何快速计算\(f(i)\)和\(g(i)\)。
考虑用\(c(i)\)表示\(i\)的倍数个数。
对于\(f(i)\)
其方案数就是在这\(c(i)\)个数中枚举一个数作为\(x\),剩余的数任选(不能为空)作为\(S\)。
即:
\[c(i)\times(2^{c(i)-1}-1)
\]
对于\(g(i)\)
枚举\(S\)的大小\(k\),那么\(x\)就有\(n-k\)种取法,暴力计算式就是:
\[\sum_{k=1}^{c(i)}C_{c(i)}^k\times(n-k)
\]
把括号拆开,式子就变成了两部分:
\[n\times \sum_{k=1}^{c(i)}C_{c(i)}^k-\sum_{k=1}^{c(i)}C_{c(i)}^k\times k
\]
对于前半部分:
\[n\times \sum_{k=1}^{c(i)}C_{c(i)}^k=n\times(2^{c(i)}-1)
\]
对于后半部分:
\[\begin{align}
\sum_{k=1}^{c(i)}C_{c(i)}^k\times k&=\sum_{k=1}^{c(i)}\frac{c(i)!}{(k-1)!\times (c(i)-k)!}\\&=c(i)\times\sum_{k=1}^{c(i)}C_{c(i)-1}^{k-1}\\&=c(i)\times\sum_{k=0}^{c(i)-1}C_{c(i)-1}^{k}\\&=c(i)\times2^{c(i)-1}
\end{align}
\]
于是这道题就做完了。
代码:\(O(V\ln V)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
#define V 10000000
#define X 1000000007
using namespace std;
int n,a[N+5],pw[N+5],f[V+5],g[V+5],c[V+5],S[N+5];
int main()
{
RI i,j;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i),++c[a[i]];
for(i=1;i<=V;++i) for(j=i<<1;j<=V;j+=i) c[i]+=c[j];//求出每个数的倍数个数
for(pw[0]=i=1;i<=n;++i) pw[i]=(pw[i-1]<<1)%X;for(i=V;i;--i)
{
f[i]=1LL*c[i]*(pw[c[i]-1]-1)%X,g[i]=(1LL*n*(pw[c[i]]-1)-1LL*c[i]*pw[c[i]-1]%X+X)%X;//计算f和g
for(j=i<<1;j<=V;j+=i) f[i]=(f[i]-f[j]+X)%X,g[i]=(g[i]-g[j]+X)%X;//因数容斥
}return printf("%d\n",(f[1]-g[1]+X)%X),0;//容斥计算答案
}
待到再迷茫时回头望,所有脚印会发出光芒