CodeForces 547 C.Mike and Foam(素因子分解+容斥)

201 篇文章 10 订阅
190 篇文章 1 订阅

Description
有n张牌,每张牌上都写着一个数字ai,开始时所有牌都在手上。现在有q个询问,每次询问指定一个数x,如果第x张牌在你手上,就要把它放在桌子上;如果在桌子上,就要拿回手中。每次这样拿完或者放完牌,要求回答当前桌子上的牌中,有多少对(i,j)(i < j),满足gcd(ai,aj)=1
Input
第一行为两个整数n和q表示牌的数量和查询数量,第二行n个整数表示每张牌上的数字,最后q行每行一个整数x表示查询
Output
对于每次查询,输出对第x张牌操作后桌子上的牌中,有多少i < j,满足gcd(ai,aj)=1
Sample Input
5 6
1 2 3 4 6
1
2
3
4
5
1
Sample Output
0
1
3
5
6
2
Solution
令ans为桌子上的牌中满足gcd(ai,aj)=1的(i,j)对数,num[i]记录桌上牌中以i为因子的牌的数量,tol记录桌上牌数
1.每在桌子上放一张牌x,ans增量为桌上原有牌中与x互素的牌的数量,那么枚举x的素因子,利用容斥原理可以求出桌上所有与x不互素的牌的数量sum,那么此时ans+=(tol-sum),tol++
2.每从桌上拿走一张牌,ans减量为拿走牌后桌上牌中与x互素的牌的数量,同样枚举x的素因子,利用容斥原理求出拿走牌后桌上所有与x不互素的牌的数量sum,那么此时tol–,ans-=(tol-sum)
Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
#define maxn 555555
ll n,q,a[maxn];
ll res,prime[maxn];
ll tol,num[2*maxn];//tol记录桌上牌数,num[i]记录桌上以i为因子的牌数 
ll ans;//ans记录答案值 
bool vis[maxn];
void get_prime(ll n)///对n素分解 
{
    res=0;
    for(ll i=2;i*i<=n;i++)
    {
        if(n%i==0)
        {
            prime[res++]=i;
            while(n%i==0)
                n/=i;
        }
    }
    if(n!=1)
        prime[res++]=n;
}
void sub(ll n)//将第n张牌从桌子上拿走 
{
    vis[n]=false;//取消标记 
    ll sum=0;//统计桌上原有牌中与n互素的牌数 
    for(ll i=1;i<(1<<res);i++)//容斥 
    {
        ll temp=1,ret=0;
        for(ll j=0;j<res;j++)
            if(i&(1<<j))
                temp*=prime[j],ret++;
        num[temp]--;//以temp为因子的牌数减一 
        if(ret%2) sum+=num[temp];
        else sum-=num[temp];
    }
    tol--;//桌上牌数减一 
    ans-=(tol-sum);
    printf("%I64d\n",ans);
    return ;
}
void add(ll n)//将第n张牌放在桌子上 
{
    vis[n]=true;//标记这张牌 
    ll sum=0;//统计桌上原有牌中与n互素的牌数 
    for(ll i=1;i<(1<<res);i++)//容斥 
    {
        int temp=1,ret=0;
        for(ll j=0;j<res;j++)
            if(i&(1<<j))
                temp*=prime[j],ret++;
        if(ret%2) sum+=num[temp];
        else sum-=num[temp];
        num[temp]++;//以temp为因子的牌数加一 
    }
    ans+=(tol-sum);
    tol++;//桌上牌数加一  
    printf("%I64d\n",ans);
    return ;
}
int main()
{
    scanf("%I64d%I64d",&n,&q);
    for(ll i=1;i<=n;i++)
        scanf("%I64d",&a[i]);
    memset(vis,false,sizeof(false));//初始化 
    memset(num,0,sizeof(num));//初始化 
    tol=0;
    ans=0;
    while(q--)
    {
        ll x;
        scanf("%I64d",&x);
        get_prime(a[x]);//对第x张牌的数字素分解 
        if(vis[x])//x已经在桌上则将其拿走 
            sub(x);
        else//x不在桌上则将其放在桌上 
            add(x);
    }
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值