51NOD 1678 lyk与gcd(容斥+素数筛)

传送门
这天,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

解题思路:
这个题目,我们可以从反面考虑,求 ij 不互素的和,然后用总和减去就得到需要的结果了。
首先,在主函数外面进行素数筛,将所有的素数筛出来,为查询的时候做准备(其实是为了容斥做准备)
然后用一个 sum 值记录 1n 所有数字的总和
在然后,我们预处理一下 1i 每个数的倍数的和,比如说 tmp[i] 表示 1n 中所有是 i 的倍数的数字的和。(为了下面进行容斥的时候方便)
然后如果进行的是第一个操作,即修改值的操作,那么我们将这个下标下的所有因子(注意是因子,不是素因子)的 tmp 值进行更新,sum值也进行更新。
如果是第二个操作,即查询操作,那么我们直接对查询的 i <script type="math/tex" id="MathJax-Element-75">i</script> 进行素因子分解,得到素因子之后,然后进行容斥求解即可,容斥过程在代码中有详细过程,就不做解释了。
代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
using namespace std;
typedef long long LL;
const int MAXN = 1e5+5;
int p[MAXN];
int prime[MAXN];
int cnt;
void isPrime(){
    memset(prime, 0, sizeof(prime));
    prime[1] = 1;
    cnt = 0;
    for(int i=2; i<MAXN; i++){
        if(!prime[i]){
            p[cnt++] = i;
            for(int j=i+i; j<MAXN; j+=i)
                prime[j] = 1;
        }
    }
}
int a[MAXN], tmp[MAXN];
int fac[MAXN/100];
int main()
{
    isPrime();
    int n, q;
    while(~scanf("%d%d",&n,&q))
    {
        int sum = 0;
        ///memset(tmp, 0, sizeof(tmp));
        for(int i=1; i<=n; i++) scanf("%d",&a[i]), sum += a[i];
        for(int i=2; i<=n; i++) for(int j=1; i*j<=n; j++) tmp[i] += a[i*j];
        while(q--){
            int op;
            scanf("%d",&op);
            if(op == 1){
                int ind, val;
                scanf("%d%d",&ind, &val);
                sum -= a[ind];
                tmp[ind] -= a[ind];
                tmp[ind] += val;
                for(int i=2; i*i<=ind; i++){
                    if(i*i == ind){
                        tmp[i] -= a[ind];
                        tmp[i] += val;
                        continue;
                    }
                    if(ind%i == 0){
                        tmp[i] -= a[ind];
                        tmp[i] += val;
                        tmp[ind/i] -= a[ind];
                        tmp[ind/i] += val;
                    }
                }
                a[ind] = val;
                sum += val;
            }
            else{
                int ind;
                scanf("%d",&ind);
                int num = 0, tp = ind;
                for(int i=0; p[i]*p[i]<=tp&&i<cnt; i++)
                    if(tp % p[i] == 0){
                        fac[num++] = p[i];
                        while(tp % p[i] == 0) tp /= p[i];
                    }
                if(tp > 1) fac[num++] = tp;
                int status = (1<<num);
                int ans = 0;
                for(int i=1; i<status; i++){
                    int tt = 1, cntt = 0;
                    for(int j=0; j<num; j++){
                        if(i&(1<<j)){
                            tt *= fac[j];
                            cntt++;
                        }
                    }
                    if(cntt & 1) ans += tmp[tt];
                    else ans -= tmp[tt];
                }
                printf("%d\n",sum - ans);
            }
        }
    }
    return 0;
}
/**
6 6
1 2 3 4 5 6
2 6
1 6 1
2 6
*/
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值