- 给定一个大小为(n)的可重集。
- 问有多少种方式从(n)中取出一个元素(x)和一个子集(S),满足(x otin S)且(gcd{S}>1,gcd(gcd{S},x)=1)。
- (nle5 imes10^5),值域为([2,10^7])。
因数容斥
应该算是一种做滥掉的套路了吧。。。
这个套路大致就是,令(f(i))表示(d)是(i)的倍数的方案数,(f'(i))表示(d=i)的方案数。
显然有(f'(i)=f(i)-sum_{i|j}f'(j)),那么只要从大到小枚举(i)做一遍就好了,且这样子做一遍的复杂度应该是(O(Vln 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) imes(2^{c(i)-1}-1)
]
对于(g(i))
枚举(S)的大小(k),那么(x)就有(n-k)种取法,暴力计算式就是:
[sum_{k=1}^{c(i)}C_{c(i)}^k imes(n-k)
]
把括号拆开,式子就变成了两部分:
[n imes sum_{k=1}^{c(i)}C_{c(i)}^k-sum_{k=1}^{c(i)}C_{c(i)}^k imes k
]
对于前半部分:
[n imes sum_{k=1}^{c(i)}C_{c(i)}^k=n imes(2^{c(i)}-1)
]
对于后半部分:
[egin{align}
sum_{k=1}^{c(i)}C_{c(i)}^k imes k&=sum_{k=1}^{c(i)}frac{c(i)!}{(k-1)! imes (c(i)-k)!}\&=c(i) imessum_{k=1}^{c(i)}C_{c(i)-1}^{k-1}\&=c(i) imessum_{k=0}^{c(i)-1}C_{c(i)-1}^{k}\&=c(i) imes2^{c(i)-1}
end{align}
]
于是这道题就做完了。
代码:(O(Vln 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
",(f[1]-g[1]+X)%X),0;//容斥计算答案
}