[Codeforces547C]Mike and Foam(莫比乌斯反演+组合数学)

来源:互联网 发布:求推荐淘宝美国代购店 编辑:程序博客网 时间:2024/05/29 15:55

题目描述

传送门
题意:给出一列数a1..an,每一次给出一个数x,将ax的状态取反(有变成没有,没有变成有,初始没有),每一次统计存在的数中gcd(ai,aj)=1(i<j)的有多少个数对。

题解

f(n)表示gcd为n的数对个数,F(n)表示gcd为n的倍数的数对个数
那么F(n)=n|df(d)
实际上我们要求的就是f(1)
那么利用反演公式f(n)=n|dμ(dn)F(d)
可以得到f(1)=dμ(d)F(d)

考虑F(n)怎么求
假设我们能求出来g(n)表示是n的倍数的数的个数
那么显然F(n)=C2g(n),这样的话F就可以O(1)
要求g的话暴力就可以…类似于埃氏筛法的复杂度分析,大概是O(nloglogn)左右?

f(1)的过程中,d看似没有上限,但是发现如果d>n的话g(d)=0,然后F(d)=0,实际上就没有意义了,所以d的上限也就为n
这样f(1)就可以O(n)求了

以上讨论的都是给出了所有的数算一遍f(1)的做法
但是这道题是有q个操作,其实比上面筛法什么的还要简单,只需要动态维护f(1)F(d)的值,每一次对当前数分解质因数就行了,速度也非常优秀
时间复杂度O(qa)

代码

#include<algorithm>#include<iostream>#include<cstring>#include<cstdio>#include<cmath>using namespace std;#define N 500005#define LL long longint n,q,a[N],p[N],prime[N],mu[N],flag[N];LL f[N],ans;void get(int n){    mu[1]=1;    for (int i=2;i<=n;++i)    {        if (!p[i])        {            prime[++prime[0]]=i;            mu[i]=-1;        }        for (int j=1;j<=prime[0]&&i*prime[j]<=n;++j)        {            p[i*prime[j]]=1;            if (i%prime[j]==0)            {                mu[i*prime[j]]=0;                break;            }            else mu[i*prime[j]]=-mu[i];        }    }}void change(int n,int opt){    for (int i=1;i*i<=n;++i)        if (n%i==0)        {            ans-=f[i]*(f[i]-1)/2*mu[i];            f[i]+=opt;            ans+=f[i]*(f[i]-1)/2*mu[i];            if (n/i==i) continue;            ans-=f[n/i]*(f[n/i]-1)/2*mu[n/i];            f[n/i]+=opt;            ans+=f[n/i]*(f[n/i]-1)/2*mu[n/i];        }}int main(){    get(500000);    scanf("%d%d",&n,&q);    for (int i=1;i<=n;++i) scanf("%d",&a[i]);    memset(flag,-1,sizeof(flag));    for (int i=1;i<=q;++i)    {        int x;scanf("%d",&x);        flag[x]=-flag[x];        change(a[x],flag[x]);        printf("%I64d\n",ans);    }}
1 0