答:欧几里得+......+
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
这段gcd代码大家一定很熟悉^_^。gcd嘛!又叫欧几里得算法,这和我们今天要说的莫比乌斯有(非常^n)密切关系! 这两天做了李总挂的一套莫比乌斯的题目深有感触。那我们赶紧看看这两个人是如何快乐的玩耍的!
1.莫比乌斯函数定义
![This is the rendered form of the equation. You can not edit this directly. Right click will give you the option to save the image, and in most browsers you can drag the image onto your desktop or another program.](https://i-blog.csdnimg.cn/blog_migrate/0fbdbaf64be71c985dee8ff1e736b305.gif)
也就是说一个数x,它的素因子分解中只要有一个素因子的幂次超过1那么它的莫比乌斯函数值直接为0,否则就按素因子的个数r,如果r是奇数那就是-1,偶数就是1,此外,一般
。
![](https://i-blog.csdnimg.cn/blog_migrate/a0ba66eb0474295b2a320713c1a9c725.gif)
这是个比较简单的函数,取值只有三种-1,1,0.先来算单个莫比乌斯函数值(这个基本不用)
int Mobius(int x)
{
if(x==1) return 1;
int cnt=0;
for(int i=2;i*i<=x;i++)
if(x%i==0)
{
cnt++;
x/=i;
if(x%i==0) return 0;//i这个素因子已经超过一个
}
if(x>1) cnt++;
return cnt%2?-1:1;
}
常用的是利用线性筛法去打表,O(n)算出一定范围内的莫比乌斯函数值。
务必理解一下线性筛,它价值不至于筛素数,而且基于线性筛可以快速算各种各样的函数值。记住线性筛素数时,每个数总是被它的最小素因子筛掉(自己手动模拟加深理解)
const int N=1e7+5;
int pri[N],tot=0,mb[N];
bool vis[N];
void init()
{
memset(vis,false,sizeof vis);
mb[1]=1;//care for it
for(int i=2;i<N;i++)
{
if(!vis[i])
{
pri[tot++]=i;
mb[i]=-1;//i 是素数
}
for(int j=0;j<tot && i*pri[j]<N;j++)
{
vis[i*pri[j]]=true;
//这里需要一种递推思想
if(i%pri[j]) mb[i*pri[j]]=-mb[i];
//素数pri[j]是i没有的,所以增加一个素数就在mb[i]基础上乘mb[pri[j]]=-1;
else {
mb[i*pri[j]]=0;//i*pri[j]至少含有两个pri[j]
break;
}
}
}
}
这个模板很常用。单纯的莫比乌斯函数就这些。
2.莫比乌斯函数的由来及简单应用
最初,莫比乌斯函数就是容斥原理在数论方面的一种应用,从代码上说就是容斥的递归形式改写成了循环的形式。
来看一个简单的问题:给n个素数p[i],求有多少个a,满足1<=a<=m && a不是任意一个p[i]的倍数(这里不考虑数据范围,假设都很少(我知道小数据可以暴力))
显然用容斥的思想很容易:设f[i]表示[1,m]内是i的倍数的个数,f[i]=m/i,然后容斥,不妨假设n=3,p[1]=2,p[2]=3,p[3]=5;
那么
,当数量较多时显然用递归写比较方便嘛!
![](https://i-blog.csdnimg.cn/blog_migrate/bb48f09ef2661b6667ced6f656cd4c5c.gif)
int ans=0;
void dfs(int pos,int choose,int tag)
{
if(pos>n)
{
ans+=tag*f(choose);
return;
}
//每个数选或者不选
dfs(pos+1,choose*p[pos],-tag);
dfs(pos+1,choose,tag)
}
我们把注意力放在tag变量上,也就是系数,只有1或-1,有些要加,有些要减,仔细观察发现当你选了奇数个素因子时是-1,偶数的话就是1,这正好就对应莫比乌斯函数,而且for循环的话,莫比乌斯函数值的0正好把无关的没计算在内,它作为系数而存在。而这个问题p[i]是离散的用莫比乌斯函数写比容斥更加麻烦,然而这都不是重点,至少你得先理解清楚莫比乌斯函数,做过容斥原理专题的,不妨看看有没有可以转换成莫比乌斯函数去做的,虽然有点牵强,因为后面的内容比较抽象、枯燥,所以务必理解莫比乌斯函数与容斥的关系。
既然莫比乌斯函数是容斥原理在数论上的一种应用,当它遇到了gcd时,就青出于蓝了!!!
3.莫比乌斯反演
在讲这个前,我先列举下,莫比乌斯反演通常能解决什么样的问题:
1.求有多少对a,b满足gcd(a,b)=1,其中1<=a<=n,1<=b<=m;都考虑有序对,(2,1)和(1,2)是不一样的,不对(1,1)是唯一的。
2.
求有多少对a,b满足gcd(a,b)是素数,其中1<=a<=n,1<=b<=m;
3.求有多少对a,b满足gcd(a,b)=k,其中1<=a<=n,1<=b<=m;
4.求有多少对a,b满足gcd(a,b)是完全平方数,其中1<=a<=n,1<=b<=m;
5.a,b的范围是一些输入的数,即离散的集合。
6.求
![](https://i-blog.csdnimg.cn/blog_migrate/1f020f4047aa4d326e7e32b934d925d7.gif)
7........
总的来说就是求一个集合(不一定每次都是[1,n])里gcd满足一些乱七八糟的条件的对数,这是计数,显然计数都可以用来求和或者求乘积,因为无非就是算一个数出现多少次,然后算贡献值,这个很重要。
先来看看反演:
设f(x)表示gcd(a,b)=x的对数(a,b都是有一定范围的),这个函数一般就是我们直接要去求的,这个一般很好设出来。
然后设:
,(新手注意里面有个数论符号 d|x 表示x是d的倍数,专业术语叫d整除x ,比如F(4)=f(1)+f(2)+f(4) )先不要管F(x)的含义,这是设F(x)的套路,也就说,f(x)是根据题目变化的,F(x)和f(x)关系确实不变的。当然设完之后就要考虑F(x)的含义了。一般通过F(x)的含义可以很容易算出F(x),后面具体题目会讲的.那么现在问题是:我们要求的是f(x),而F(x)很好求,这个时候就用反演,用好求的F(x),来表示f(x);
![](https://i-blog.csdnimg.cn/blog_migrate/7325ce182a107d5505b2248841e69490.gif)
![This is the rendered form of the equation. You can not edit this directly. Right click will give you the option to save the image, and in most browsers you can drag the image onto your desktop or another program.](https://i-blog.csdnimg.cn/blog_migrate/f0a0f6781841f452511d95ec901b821d.gif)
不过这个反演公式似乎不常用,还有一个:
![This is the rendered form of the equation. You can not edit this directly. Right click will give you the option to save the image, and in most browsers you can drag the image onto your desktop or another program.](https://i-blog.csdnimg.cn/blog_migrate/282202131246b0fdb7f0e8aa4175ee67.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/11cb478d1b1220c49bccbd6a231065ed.gif)
来个例题:
求有多少对a,b满足gcd(a,b)=1,其中1<=a<=n,1<=b<=m;最基础最简单的一个
设f(x)表示gcd(a,b)=1的对数,1<=a<=n,1<=b<=m
设F(x):
,当然d肯定不超过min(n,m),先看F(x)的含义,首先f(d)是gcd=d对数,而d是x的倍数,那么F(x)就是gcd(a,b)是x倍数的对数,只要一对(a,b)的gcd是x的倍数就统计在内。
![](https://i-blog.csdnimg.cn/blog_migrate/11cb478d1b1220c49bccbd6a231065ed.gif)
求F(x):既然gcd(a,b)是x的倍数,那么a=x*i,b=x*j,只要a,b不越界,i,j可以任意取值,i,j可以看成是x的倍数,记住我们是要计数,有多少对(i,j),这个时候条件已经没有什么限制,[1,n]肯定有
个i满足条件,同理[1,m]是
个j
![](https://i-blog.csdnimg.cn/blog_migrate/545be6947b8b81dea34f4805aa207147.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/b62a456372c4cf19058855eab1a894e3.gif)
先组合就是任选i,j就是两个相乘,所以
,一下就算出来了
![](https://i-blog.csdnimg.cn/blog_migrate/977a1bb3838078db941a8a242d8d8e00.gif)
用反演公式:
,然后要求的是f(1)值
![](https://i-blog.csdnimg.cn/blog_migrate/282202131246b0fdb7f0e8aa4175ee67.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1d7b817dbfede5b0a8b2731a60ce2127.gif)
仔细观察这个式子,回想前面说的容斥与莫比乌斯函数的关系,这个和式就是容斥的循环版本:
比如令min(n,m)=3吧,d=1就是ans+=F(1),结合F(x)的含义,就是加所有gcd是1的倍数的数,然后d=2是mb[2]=-1;
ans+=-F(2),减去gcd是2的倍数的,最后减去gcd是3的倍数的。。哈哈,是容斥吧!还不理解就手动写几个看看。
当然这里F(d)里面是一个整除,所以循环可以分块加速,复杂度O(sqrt(n)),具体方法查看分块加速
求gcd为素数的对数,a,b,都是[1,n],n<=1e7.
这个上一题一样的反演公式
,得到反演公式后就是枚举当x为素数时的答案在求和就是行了
![](https://i-blog.csdnimg.cn/blog_migrate/282202131246b0fdb7f0e8aa4175ee67.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/0e5ce285df7bb890fca47550de546ea8.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/8f16fd4c0aefe61a4c82ca3ec623196d.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/59f6bff37581dbe1bf98078762330106.gif)
最终
,既然把T[d]来出来,显然我是要打表咯,这里线性筛就发挥作用了。
![](https://i-blog.csdnimg.cn/blog_migrate/638ccf83d1c9a2a918f24c1c76a79f1c.gif)
其实还是挺麻烦的。
就和筛莫比乌斯函数一样,代码中我们只要考虑三个地方就可以了,i是素数,i%pri[j]!=0 和i%pri[j]==0的情况。
最好自己先想想。
1.i是素数很简单。能得到T[i]的值
2.if(i%pri[j])我们算T[i*pri[j] ],按照求和公式需要考虑它(注意不是i而是i*pri[j])每个素因子,不妨设为pi,显然pri[j]本身就是某一个pi,既然是求和,当pi==pri[j]时
,否则把i/pi看成整体,利用莫比乌斯函数的积性:
![](https://i-blog.csdnimg.cn/blog_migrate/52401e8b997c19ba3f1a5541b88cc200.gif)
u[i*pri[j]]=u[i]*u[pri[j]]=-u[i](注意前提是i%pri[j]!=0),所以
,最后两种加起来
![](https://i-blog.csdnimg.cn/blog_migrate/f9ff8c885735b7850b68c22c50ae4e13.gif)
T[i*pri[j]]=u[i]-T[i];
3.if(i%pri[j]==0)同上讨论pi,当pi==pri[j]时与分母抵消一个pri[j],就等于u[i],其他的就简单了pri[j]抵消不了,那i*pri[j]的pri[j]的幂次超过1那就结果直接是0,所以u[i*pri[j]]=u[i].
当然这个显然也要求T[i]的前缀和,因为要对F[d]分块
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N=1e7+9;
typedef long long ll;
int pri[N],tot=0;
int mb[N];
bool vis[N];
int sum[N];
void init()
{
memset(vis,false,sizeof vis);
mb[1]=1;
sum[0]=0;//这里偷个懒,sum[i]筛素数时就是刚刚说的T[i],然后在求一个前缀和
sum[1]=0;
for(int i=2; i<N; i++)
{
if(!vis[i])
{
pri[tot++]=i;
mb[i]=-1;
sum[i]=1;
}
for(int j=0; j<tot && i*pri[j]<N; j++)
{
vis[i*pri[j]]=true;
if(i%pri[j])
{
mb[i*pri[j]]=-mb[i];
sum[i*pri[j]]=mb[i]-sum[i];
}
else
{
mb[i*pri[j]]=0;
sum[i*pri[j]]=mb[i];
break;
}
}
}
for(int i=1; i<N; i++) //前缀和
sum[i]+=sum[i-1];
}
int main()
{
int n;
init();
scanf("%d",&n);
ll ans=0;
for(int i=2,la=0; i<=n; i=la+1)
{
la=n/(n/i);
ans+=(ll)(sum[la]-sum[i-1])*(n/i)*(n/i);
}
printf("%lld\n",ans);
return 0;
}
再来一道同类型的 HDU 5663,和上一题类似,枚举平方数然后方法都一样
![](https://i-blog.csdnimg.cn/blog_migrate/c778aa332ad391da77ff5a32e62243b1.gif)
看起来就是个很简单的公式,只有n是1时那么莫比乌斯和才为1,其他都是0.这公式用来装换gcd
再看一个和式小标的写法:
![](https://i-blog.csdnimg.cn/blog_migrate/1c2dacf9c5fbc0a56b3e340719523864.gif)
bzoj 2154 这题求
![](https://i-blog.csdnimg.cn/blog_migrate/28cc6976b8bc92e575012638678e6d1c.gif)
我们知道lcm(a,b)=a*b/gcd(a,b);
设i=a*gcd(i,j),j=b*gcd(i,j)
那么我们枚举gcd的话,假设枚举gcd=d,此时gcd(i,j)=d的条件显然就是gcd(i/d,j/d)=1,我们在和式后面乘上一个布尔表达式使之正确:
![](https://i-blog.csdnimg.cn/blog_migrate/138eb32b509a442e6d370331aa9fade1.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/003508b136c13a9359d0aeb97735a31a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/d954605316cf2d99f9c2dc460ce241d2.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6afb0cc10f10b3b5a989bdd4b4b47e28.gif)
#pragma comment(linker, "/STACK:10240000,10240000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<set>
#include<vector>
#include<map>
#include<stack>
#include<cmath>
#include<algorithm>
using namespace std;
const double R=0.5772156649015328606065120900;
const int N=1e7+5;
const int mod=20101009;
const int INF=0x3f3f3f3f;
const double eps=1e-8;
const double pi=acos(-1.0);
typedef long long ll;
int pri[N],tot=0,mb[N];
bool vis[N];
ll sum[N];
void init(int n)
{
n++;
memset(vis,false,(n+2)*sizeof(vis[0]));
sum[0]=0;
mb[1]=1;
sum[1]=1;
for(int i=2;i<n;i++)
{
if(!vis[i]) {
mb[i]=-1;
pri[tot++]=i;
}
for(int j=0;j<tot && i*pri[j]<n;j++)
{
vis[i*pri[j]]=true;
if(i%pri[j]) mb[i*pri[j]]=-mb[i];
else {
mb[i*pri[j]]=0;
break;
}
}
sum[i]=sum[i-1]+(ll)mb[i]*i*i;//打表
sum[i]%=mod;
}
}
ll ff(ll n)//等差求和
{
return (ll)n*(n+1)/2%mod;
}
ll get(int n,int m)
{
ll ans=0;
for(int i=1,la=0;i<=n;i=la+1)//枚举t,分块
{
la=min(n/(n/i),m/(m/i));
ans=(ans+((sum[la]-sum[i-1])*ff(n/i)%mod)*ff(m/i))%mod;
}
return ans;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
if(n>m) n^=m^=n^=m;
init(n);
ll ans=0;
for(ll i=1,la=0;i<=n;i=la+1)//枚举d
{
la=min(n/(n/i),m/(m/i));
ll t=get(n/i,m/i);//每一个d都要去算里面的和式
ans=(ans+t*((la+i)*(la-i+1)/2%mod))%mod;//这里d的一个前缀就是等差嘛
}
ans+=mod;
ans%=mod;
printf("%lld\n",ans);
return 0;
}
bzoj 3994 &codeforces 235E
这两题要用同一个定理
d(n)表示n的约数个数
![](https://i-blog.csdnimg.cn/blog_migrate/dc9121142f5dc0806330408305278205.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/941872439b7a3995221a14f084ee68db.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f6de9e31d0b997549902a748c9b5fbdf.gif)
235E代码:(代码很丑,建议理解题解,然后自己写)
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N=2005;
const int mod=1073741824;
typedef long long ll;
vector<int>g[N];
int dp[N][N];
int gcd(int a,int b)
{
if(b==0) return a;
if(dp[a][b]!=-1) return dp[a][b];
return dp[a][b]=gcd(b,a%b);
}
int mb[N];
int pri[N],tot=0;
bool vis[N];
void init()
{
memset(vis,false,sizeof vis);
mb[1]=1;
for(int i=2;i<N;i++)
{
if(!vis[i]) {
pri[tot++]=i;
mb[i]=-1;
}
for(int j=0;j<tot && i*pri[j]<N;j++)
{
vis[i*pri[j]]=true;
if(i%pri[j]) mb[i*pri[j]]=-mb[i];
else {
mb[i*pri[j]]=0;
break;
}
}
}
memset(dp,-1,sizeof dp);
for(int i=1;i<N;i++)
{
for(int j=1;j<N;j++)
if(gcd(i,j)==1) g[i].push_back(j);//预处理因子
}
}
int main()
{
int a,b,c;
init();
scanf("%d%d%d",&a,&b,&c);
ll ans=0;
int up=min(b,c);
//其实就直接三重循环枚举
for(int i=1;i<=a;i++)//one
{
int n=g[i].size();
ll sum=0;
for(int ii=0;ii<n && g[i][ii]<=up;ii++)//two
{
int d=g[i][ii];
if(mb[d]==0) continue;
ll t1=0;
int up1=b/d;
ll tmp=mb[d];
for(int jj=0;jj<n && g[i][jj]<=up1;jj++)//three
t1=(t1+up1/g[i][jj])%mod;
tmp=tmp*t1%mod;
int up2=c/d;
ll t2=0;
for(int kk=0;kk<n && g[i][kk]<=up2;kk++)//three
t2=(t2+up2/g[i][kk])%mod;
tmp=tmp*t2%mod;
sum=(sum+tmp)%mod;
}
ans=(ans+a/i*sum)%mod;
}
printf("%I64d\n",ans);
return 0;
}
HDU 4947
这题主要是要用树状数组维护。
对于每一个更新操作可以看做a[x]+=v*[gcd(x,n)==d),令x=d*i,n=d*j;所以gcd(i,j)==1,依旧是把比尔表示换成和式
![](https://i-blog.csdnimg.cn/blog_migrate/9c654fc32fc1109d7b045420d0fbe745.gif)
我们还是枚举t,但是我想一次就把所有满足条件的x更新完,我对a[x]进行拆分:
![](https://i-blog.csdnimg.cn/blog_migrate/72b3ca7275ffd98152a5e619eefa1304.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/05fcb59be1d3aec13be5da2c6cbc8806.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/c69c1f5da57364f18a2d5daf1ecba4db.gif)
#pragma comment(linker, "/STACK:10240000,10240000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<set>
#include<vector>
#include<map>
#include<stack>
#include<cmath>
#include<algorithm>
using namespace std;
const double R=0.5772156649015328606065120900;
const int N=1e5+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-8;
const double pi=acos(-1.0);
typedef long long ll;
const int M=2e5+5;
int pri[M],tot=0,mb[M];
bool vis[M];
vector<int>g[M];
void init()
{
memset(vis,false,sizeof vis);
mb[1]=1;
for(int i=2;i<M;i++)
{
if(!vis[i]){
mb[i]=-1;
pri[tot++]=i;
}
for(int j=0;j<tot && i*pri[j]<M;j++)
{
vis[i*pri[j]]=true;
if(i%pri[j]) mb[i*pri[j]]=-mb[i];
else {
mb[i*pri[j]]=0;
break;
}
}
}
for(int i=1;i<M;i++)
{
for(int j=i;j<M;j+=i)
g[j].push_back(i);
}
}
ll f[N];
ll sum(int i)
{
ll s=0;
while(i>0) {
s+=f[i];
i-=i&-i;
}
return s;
}
int ri;
void add(int i,ll x)
{
while(i<=ri)
{
f[i]+=x;
i+=i&-i;
}
}
void update(int n,int d,ll v)
{
if(n%d) return;
n=n/d;
int re=g[n].size();
for(int i=0;i<re;i++)//枚举每个n/d的因子t去更新f[t*d]
{
int j=g[n][i];
if(j*d>ri) break;
add(j*d,v*mb[j]);
}
}
ll query(int n)
{
ll ans=0;
for(int i=1,la=0;i<=n;i=la+1)
{
la=n/(n/i);
ans+=(ll)(sum(la)-sum(i-1))*(n/i);
}
return ans;
}
int main()
{
init();
int q;
int kase=1;
while(~scanf("%d%d",&ri,&q) && ri+q)
{
printf("Case #%d:\n",kase++);
memset(f,0,(ri+10)*sizeof(f[0]));
while(q--)
{
int typ;
scanf("%d",&typ);
int n,d,v;
if(typ==1){
scanf("%d%d%d",&n,&d,&v);
update(n,d,v);
}else {
scanf("%d",&n);
printf("%I64d\n",query(n));
}
}
}
return 0;
}
/*
*/
现在我们来看一些离散的问题
面对这类问题一定要明确一个事实:n的因子个数不会很多,比如n<=1e5是,最多也就128个,自己打表试试吧!
这类问题总是要把集合里的数按照因子倍数分类,即f[i]表示i的倍数的个数,然后用容斥的思想,我一般写法是莫比乌斯函数
题意就是说给出n个数a[1]...a[n],刚开始集合是空的,然后有q个查询,每个查询输入一个下标x,如果集合中已经有第x个元素a[x]了就把它删掉,否则就放进去,求每次操作后集合元素中右多少对互质的。
有种dp思想,如果每次对集合元素求对数的话很难,那就考虑每次操作的那个数对答案的影响,最初是0,插入一个数就看这个元素能增加多少对互质的数,删除也是要更新被删掉数的影响的,那么问题就变成了一个数与一个集合里的数有多少互质,我们保存集合里的元素的f[i]值:是i的倍数的有多少个,利用f[i]值容斥一下就可以了,枚举a[x]的因子,对 f[ a[x]的因子 ] 容斥,当然莫比乌斯函数就可以了。
#pragma comment(linker, "/STACK:10240000,10240000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<set>
#include<vector>
#include<map>
#include<stack>
#include<cmath>
#include<algorithm>
using namespace std;
const double R=0.5772156649015328606065120900;
const int N=5e5+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-8;
const double pi=acos(-1.0);
typedef long long ll;
vector<int>g[N];
int f[N];
int a[N];
int pri[N],tot=0,mb[N];
bool vis[N],has[200009];
void init()
{
memset(vis,false,sizeof vis);
mb[1]=1;
for(int i=2;i<N;i++)
{
if(!vis[i])
{
mb[i]=-1;
pri[tot++]=i;
}
for(int j=0;j<tot && i*pri[j]<N;j++)
{
vis[i*pri[j]]=true;
if(i%pri[j]) mb[i*pri[j]]=-mb[i];
else {
mb[i*pri[j]]=0;
break;
}
}
}
}
void resolve(int x)
{
if(g[x].size()!=0) return;//已经分解了的数不用再分解了
int up=sqrt(1.0*x);
for(int i=1;i<=up;i++)
if(x%i==0)
{
g[x].push_back(i);
if(i*i!=x) g[x].push_back(x/i);
}
}
void Insert(int x,ll& ans)
{
resolve(x);
int re=g[x].size();
for(int i=0;i<re;i++)
{
int d=g[x][i];
ans+=f[d]*mb[d];
f[d]++;//更新f[d]
}
}
void Delete(int x,ll& ans)
{
resolve(x);
int re=g[x].size();
for(int i=0;i<re;i++)
{
int d=g[x][i];
f[d]--;
ans-=f[d]*mb[d];//和Insert类似
}
}
int main()
{
init();
int n,q;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
memset(f,0,sizeof f);
memset(has,false,sizeof has);
ll ans=0;//持续更新
while(q--)
{
int x;
scanf("%d",&x);
if(has[x]) {
Delete(a[x],ans);
has[x]=false;//标记是否存在
} else {
Insert(a[x],ans);
has[x]=true;
}
printf("%I64d\n",ans);
}
return 0;
}
/*
*/
是