【Codeforces 585E】Present for Vitalik the Philatelist
莫比乌斯反演,狄利克雷前/后缀和
题目大意:
给定 $n$ 个数 $a_1,a_2,a_3,\dots,a_n$,每个数在 $[2,10^7]$ 之间。
现在要选择一个下标集合 $S$ 和一个下标 $x$,满足:$x\not\in S;\gcd_{i\in S}\{a_{i}\}>1;\gcd(x,\gcd_{i\in S}\{a_{i}\})=1$。
求满足条件的 $(S,x)$ 的个数。
题解:
我数学是真的菜啊
令 $s_n$ 表示最大公因数恰好为 $n$ 的集合个数,$f_n$ 表示与 $n$ 互质的数的个数。
那么我们要求的就是 $\sum_i s_if_i$.
考虑求出 $s$ 和 $f$。
考虑计算 $f_i$。令 $c_i$ 表示数 $i$ 的出现次数。
强行莫比乌斯反演qwq
我们令 $g_k=\sum\limits_{k|i}c_i$;$g’_k=\mu(k)g_k$,那么 $f_n=\sum\limits_{d|n} g’_d$
发现这个 $g_i$ 就表示 $i$ 的倍数的出现次数。
再考虑求 $s_i$。我们令 $s’_i$ 表示最大公因数为 $i$ 的倍数的方案数。要使最大公因数为 $i$ 的倍数,那么每个选的数必须是 $i$ 的倍数。所以容易得到 $s’_i=2^{g_i}-1$
我们又有 $s’_i=\sum\limits_{i|d}s_d$
观察上面的式子的形式,他们都是这样的形式:
其中,上面的式子为狄利克雷前缀和,下面的式子为狄利克雷后缀和。
这两个东西,都可以在 $O(w\log\log w)$ 的时间复杂度内完成。具体做法请见我贡献的【模板】Dirichlet 前缀和(后缀和与前缀和的做法本质相同,都是高维前缀和)。
计算 $g$ 和 $f$ 都是标准的狄利克雷前/后缀和形式。
但还有个问题,已知 $a$ 求 $b$ 能做了,但是那个 $s$ 的计算,是已知 $b$ 求 $a$ 的。
前面是加,那现在就把它逆回去就好了。具体就是把两重循环的反向,然后把加法改成减法就可以辣。当然也可以用高维前缀和的知识解释。
总时间复杂度 $O(n+w\log\log w)$。
Code:
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=5e5+5,M=1e7+2,md=1e9+7;
int S[M],_2[500005],a[500005],tot[M],g[M],pri[M/10],cnt,mu[M];
bool vis[M];
inline void upd(int&a){a+=a>>31&md;}
int n,lim,ans;
void sieve(const int n){
mu[1]=1;
for(int i=2;i<=n;++i){
if(!vis[i])pri[++cnt]=i,mu[i]=-1;
for(int j=1;j<=cnt&&i*pri[j]<=n;++j){
vis[i*pri[j]]=1;
if(i%pri[j]==0){
mu[i*pri[j]]=0;
break;
}else mu[i*pri[j]]=-mu[i];
}
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
for(int i=*_2=1;i<=500000;++i)upd(_2[i]=_2[i-1]*2-md);
cin>>n;
for(int i=1;i<=n;++i)
cin>>a[i],++tot[a[i]];
lim=*max_element(a+1,a+n+1);
sieve(lim);
for(int i=1;i<=cnt;++i)
for(int j=lim/pri[i];j;--j)
tot[j]+=tot[j*pri[i]];
for(int i=1;i<=lim;++i)g[i]=tot[i]*mu[i];
for(int i=1;i<=cnt;++i)
for(int j=1;j*pri[i]<=lim;++j)
g[j*pri[i]]+=g[j];
for(int i=1;i<=lim;++i)S[i]=_2[tot[i]]-1;
for(int i=cnt;i;--i)
for(int j=1;j*pri[i]<=lim;++j)
upd(S[j]-=S[j*pri[i]]);
for(register int i=lim;i>1;--i)
ans=(ans+(LL)S[i]*g[i])%md;
cout<<ans<<'\n';
return 0;
}