51Nod 1678 - lyk与gcd(分解因子+容斥)

题目链接 https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1678

【题目描述】
这天,lyk又和gcd杠上了。
它拥有一个nn个数的数列,它想实现两种操作。
1:将 aiai 改为 bb
2:给定一个数i,求所有 gcd(i,j)=1gcd(i,j)=1 时的 ajaj 的总和

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

【思路】
首先这题要从反面考虑,对于每个ii求出j[1,n]j∈[1,n]所有gcd(i,j)!=1gcd(i,j)!=1的数,然后用总和减去这些数对应的a[j]a[j]的值的和,难点在于用一个数组 c[x]c[x] 来表示所有 xx 的倍数k对应的 a[kx]a[kx]的和,也就是

c[x]=k=1kx<=na[kx]c[x]=∑k=1kx<=na[kx]
有了这个数组就方便更新和查询了,每次更新的时候不只是把数组 aa 和总和 sumsum 更新,还要把 cc 数组也更新,更新 a[id]a[id]时,枚举 idid的所有因子(1不用),然后把对应因子 vv c[v]c[v]也更新即可
查询的时候用容斥原理,某个数 jj和输入的 idid不互素说明 gcd(id,j)gcd(id,j)一定是一个 idid的因子,对 idid做唯一分解,找出所有素因子,然后枚举素因子的所有组合情况,如果是奇数个素因子相乘所得的倍数那么答案就加上这一部分,否则减去

#include<bits/stdc++.h>
using namespace std;

const int maxn=100005;

int n,q;
int a[maxn],sum;
int c[maxn];
int fac[maxn],num;

int main(){
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        sum+=a[i];
    }
    for(int i=1;i<=n;++i){
        for(int j=1;i*j<=n;++j){
            c[i]+=a[i*j];
        }
    }
    while(q--){
        int tp;
        scanf("%d",&tp);
        if(tp==1){
            int id,val;
            scanf("%d%d",&id,&val);
            c[id]+=val-a[id];
            int m=sqrt(id)+0.5;
            for(int i=2;i<=m;++i){
                if(id%i==0){
                    c[i]+=val-a[id];
                    if(id/i!=i) c[id/i]+=val-a[id];
                }
            }
            sum+=val-a[id];
            a[id]=val;
        }
        else{
            int id;
            scanf("%d",&id);
            int m=sqrt(id)+0.5;
            num=0;
            for(int i=2;i<=m;++i){
                if(id%i==0){
                    fac[num++]=i;
                    while(id%i==0) id/=i;
                    if(id==1) break;
                }
            }
            if(id>1){ fac[num++]=id; }

            int ans=0;
            for(int s=1;s<(1<<num);++s){
                int cnt=0;
                int tmp=1;
                for(int j=0;j<num;++j){
                    if(s>>j&1){
                        tmp*=fac[j];
                        ++cnt;
                    }
                }
                if(cnt&1) ans+=c[tmp];
                else ans-=c[tmp];
            }
            printf("%d\n",sum-ans);
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/wafish/p/10465185.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值