codeforces #305 547C C. Mike and Foam(莫比乌斯反演)

题目链接:

点击打开链接

题目大意:

给出一列数,最开始集合 为空,然后q次操作,每次给出一个x,如果第x个数存在,那么删去,不存在添加,问操作完互质的数有多少对

题目分析:

问互质的数的对数,裸裸的数论题

首先f(k)定义为gcd(ai,aj)(1<=i<j<=n) == k的对数

定义F(k)为k|gcd(ai,aj)(1<=i<j<=n)的对数

那么根据莫比乌斯反演定理的第二种形式得到f(k) = sigma(d>=1)[u(d)*F(d*k)]

定义cnt[i]为集合中是i的倍数的数有多少个

那么F(k) = C(2 , cnt[k] ) 结论很显然

所以我们可以利用莫比乌斯反演,在每次根据要添加或删除的数修正结果,然后输出即可

也就是对给出的数分解质因数,然后因为如果当前数含有某个质因数的2次方及以上,u等于0

而且因为2*3........*17 > 500000,所以质因数的个数不会超过6个,只要枚举2^6种影响到的F(k)即可

总的复杂度q*2^6,绝对的秒出

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
#define MAX 500007

using namespace std;

typedef long long LL;

int n,q,x;
int a[MAX];
int u[MAX];
int used[MAX];
int cnt[MAX];
int maxPrime[MAX];
vector<int> v;

LL ans;

void init ( )
{
    memset ( maxPrime , -1 , sizeof ( maxPrime ) );
    for ( int i = 1 ; i < MAX ; i++ ) u[i] = 1;
    for ( int i = 2 ; i < MAX ; i++ )
    {
        if ( ~maxPrime[i] ) continue;
        for ( int j = 1 , t = i ; t < MAX ; j++,t += i )
        {
            maxPrime[t] = i;
            u[t] = j%i==0?0:-u[t];
        }
    }   
}

void update ( int x , int f )
{
    v.clear();
    while ( x > 1 )
    {
        int p = maxPrime[x];
        v.push_back(p);
        while ( x%p == 0 )
            x /= p;
    }
    int m = v.size();
    int total = 1<<m;
    for ( int i = 0 ; i < total ; i++ )
    {
        int temp = 1;
        for ( int j = 0 ; j < m ; j++ )
            if ((1<<j)&i)
                temp *= v[j];
        //cout << "temp : " << temp << endl;
        if ( !f )
        {
            //cout <<"YES :" <<  ans <<  endl;
            ans += cnt[temp]*u[temp];
            //cout << "ANS : " << ans << endl;
            cnt[temp]++;
        }
        else
        {
            cnt[temp]--;
            ans -= cnt[temp]*u[temp];
        }
    }
}

int main ( )
{
    init();
    while ( ~scanf ( "%d%d" , &n , &q ) )
    {
        for ( int i = 0 ; i < n ; i++ )
            scanf ( "%d" , &a[i] );
        memset ( used , 0 , sizeof ( used ) );
        ans = 0; 
        while ( q-- )
        {
            scanf ( "%d" , &x );
            update ( a[x-1] , used[x-1] );
            used[x-1] ^= 1;
            printf ( "%lld\n" , ans );
        }
    }
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值