51nod 1678 lyk与gcd(容斥原理)

基准时间限制:2 秒 空间限制:131072 KB 分值: 80  难度:5级算法题

这天,lyk又和gcd杠上了。
它拥有一个n个数的数列,它想实现两种操作。


1:将  ai 改为b。
2:给定一个数i,求所有 gcd(i,j)=1 时的  aj  的总和。

Input
第一行两个数n,Q(1<=n,Q<=100000)。
接下来一行n个数表示ai(1<=ai<=10^4)。
接下来Q行,每行先读入一个数A(1<=A<=2)。
若A=1,表示第一种操作,紧接着两个数i和b。(1<=i<=n,1<=b<=10^4)。
若B=2,表示第二种操作,紧接着一个数i。(1<=i<=n)。
Output
对于每个询问输出一行表示答案。
Input示例
5 3
1 2 3 4 5
2 4
1 3 1
2 4
Output示例
9
7


想到了容斥原理,但只写过最基础的 对一个数,容斥原理求与这个数互斥的个数,求互斥的数组还是第一次见(其实是容斥没怎么学会,逃)


首先开一个sum[i]用于记录 数组中索引为i的倍数的和

更新x位置时,只需要将所有包含x这个位置的所有索引更新

查询的时候,要找与i互质的所有位置,我们可以先分解出i的所有素因子,然后对素因子枚举子集,得到素因子组合的每一种情况,此时就可以用容斥原理(包含一个素因子-包含两个素因子+包含三个素因子...)求出所有与i不互素的位置的和,然后用总和减去即可

具体看代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+200;
int n,q;
int sum[N],num[N],tot;
vector<int>v;
void init()
{
    for(int i=1;i<=n;i++)
        for(int j=1;i*j<=n;j++)
            sum[i]+=num[i*j];
}
void update(int x,int y)
{
    tot-=num[x];
    for(int i=1;i*i<=x;i++)
    {
        if(x%i==0)
        {
            if(i*i!=x)
            sum[x/i]+=y-num[x];
            sum[i]+=y-num[x];
        }
    }
    num[x]=y;
    tot+=num[x];
}
void getprime(int x)
{
    v.clear();
    for(int i=2;i*i<=x;i++)
    {
        if(x%i==0)
        {
            v.push_back(i);
            while(x%i==0)x/=i;
        }
    }
    if(x>1)v.push_back(x);
}
int query(int x)
{
    getprime(x);
    int res=0,len=v.size();
    for(int i=1;i<(1<<len);i++)
    {
        int cnt=0,t=1;
        for(int j=0;j<len;j++)
        {
            if(i&(1<<j))
            {
                t*=v[j];
                cnt++;
            }
        }
        if(cnt&1)
        res+=sum[t];
        else
        res-=sum[t];
    }
    return res;
}
int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&num[i]);
        tot+=num[i];
    }
    init();
    int op,x,y;
    while(q--)
    {
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d%d",&x,&y);
            update(x,y);
        }
        else
        {
            scanf("%d",&x);
            printf("%d\n",tot-query(x));
        }
    }

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值