题目描述
传送门
题意:给出一列数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
就可以
要求
g
的话暴力就可以…类似于埃氏筛法的复杂度分析,大概是
求
f(1)
的过程中,
d
看似没有上限,但是发现如果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 long
int 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);
}
}