gcd(伪)莫比乌斯反演(真)

今天的NOIP模拟中出现了一道奇怪的题目。。。它让我明白了考试的时候脑洞要大。。。做不出就不要大意地先反演一波

对于做过了反演题的人来说,提到了gcd、计数还看不出它和反演有关,是应该要被打的。。。所以我要记录下来引以为戒。

gcd
【题目描述】
有 n 个正整数 x1~xn,初始时状态均为未选。
有 m 个操作,每个操作给定一个编号 i,将 xi 的选取状态取反。
每次操作后,你需要求出选取的数中有多少个互质的无序数对。
【输入数据】
第一行两个整数 n,m。第二行 n 个整数 x1~xn。接下来 m 行每行
一个整数。
【输出数据】
m 行,每行一个整数表示答案。

(1<=n,m<=100000,1<=x[i]<=500000)

设f[k]=sum(i,j) (gcd(x[i],x[j])==k) ,则f[k]=sum(miu(d)*sum(i,j)); (d*k | gcd(x[i],x[j]))

设g[k]=sum(i,j) (k|gcd(x[i],x[j])),则f[k]=sum(miu(d)*g(d*k));

设s[k]=sum(i) (k|x[i]),则g[k]=s[k]*(s[k]-1)/2;所以ans=f[1]=sum(miu(d)*g(d));更新时根号维护s[i]->维护g[i]->维护f[1]即可。

#include<bits/stdc++.h>
using namespace std;
#define rep(x,y,z) for (int x=y; x<=z; x++)
#define downrep(x,y,z) for (int x=y; x>=z; x--)
#define ms(x,y,z) memset(x,y,sizeof(z))
#define LL long long
#define repedge(x,y) for (int x=hed[y]; ~x; x=edge[x].nex)
inline int read(){
	int x=0; int w=0; char ch=0;
	while (ch<'0' || ch>'9') w|=ch=='-',ch=getchar();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return w? -x:x;
}
const int maxn=500005;
int miu[maxn],prime[maxn],N,n,m,x[maxn],a[maxn];
LL g[maxn],s[maxn];
bool pd[maxn];
void Euler(int n){
	miu[1]=1; int num=0;
	rep(i,2,n){
		if (!pd[i]){ prime[++num]=i; miu[i]=-1; }
		rep(j,1,num){
			LL t=i*1LL*prime[j]; if (t>n) break;
			pd[t]=1; miu[t]=(!(i%prime[j]))? 0:((-1)*miu[i]); 
			if (!(i%prime[j])) break;
		}
	}
}
int main(){
	//freopen("gcd.in","r",stdin);
	//freopen("gcd.out","w",stdout);
	n=read(); m=read(); rep(i,1,n){ x[i]=read(); N=max(N,x[i]); }
	Euler(N); LL ans=0;
	rep(i,1,m){
		int k=read(); a[k]^=1; int tmp=(a[k])? 1:(-1);
		rep(j,1,floor(sqrt(x[k])))
		if (!(x[k]%j)) { 
		   s[j]+=tmp; ans-=miu[j]*g[j]; g[j]=s[j]*(s[j]-1)/2; ans+=miu[j]*g[j];
		   if (x[k]!=j*j){ s[x[k]/j]+=tmp; ans-=miu[x[k]/j]*g[x[k]/j];
		   g[x[k]/j]=s[x[k]/j]*(s[x[k]/j]-1)/2; ans+=miu[x[k]/j]*g[x[k]/j]; }
		}
		printf("%lld\n",ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/shiveringkonnyaku/article/details/82790884