51Nod1678 lyk与gcd

题目链接

  • 题意
    这天,lyk又和gcd杠上了。
    它拥有一个n个数的数列,它想实现两种操作。
    1:将 ai 改为b。
    2:给定一个数i,求所有 gcd(i,j)=1 时的 aj 的总和。
  • 输入
    第一行两个数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)。
  • 输出
    对于每个询问输出一行表示答案。
  • 输入样例
5 3
1 2 3 4 5
2 4
1 3 1
2 4
  • 输出样例
9
7
  • 题解
    首先思考没有修改操作,只有操作2。
    ∑ j = 1 n a [ j ] ∗ [ g c d ( i , j ) = = 1 ] \sum_{j=1}^{n}a[j]*[gcd(i,j)==1] j=1na[j][gcd(i,j)==1]等价于
    ∑ j = 1 n a [ j ] − ∑ j = 1 n a [ j ] [ g c d ( i , j ) ≠ 1 ] \sum_{j=1}^{n}a[j]-\sum_{j=1}^{n}a[j][gcd(i,j)\neq1] j=1na[j]j=1na[j][gcd(i,j)=1]
    用ans来表示答案,首先ans为前面一部分,这很好计算,现在来研究后面的一部分,它表示就是下标与 i 不互质的a[j]的和。i 与 j 不互质则表明 i 与 j 拥有共同的质因子。对 i 进行质因数分解,记录用数组 p 所有的质因数,然后利用二进制枚举其质因子所构成的所有的 i 的约数 d ,用cnt记录每个约数 d 所包含质因子的个数, 显然,当 j 是 d 的倍数时,j 与 i 拥有共同质因子,所以它们不互质,再根据容斥原理,对于所有 j 满足是 d 的倍数时, a n s = a n s + ( − 1 ) c n t ∗ a [ j ] ans=ans+(-1)^{cnt}*a[j] ans=ans+(1)cnta[j]。但是不加优化的话是会 T 的。
    ∑ i = 1 n n i \sum_{i=1}^{n}\frac{n}{i} i=1nin 是 nln(n) 级别的
    定义数组sum[i], 表示下标是i的倍数的 a[j] 的和,这样可以O(nlogn)与处理出sum数组,那么就可以节省掉枚举d的倍数的时间, a n s = a n s + ( − 1 ) c n t ∗ s u m [ d ] ans=ans+(-1)^{cnt}*sum[d] ans=ans+(1)cntsum[d]
    最后一个问题,如何考虑修改 a[i]=b 之后更新 sum 数组,我们可以 O ( n ) O(\sqrt{n}) O(n )级别找到 i 的约数 d ,修改对应的 sum[d] = sum[d] + b- a[i],最后将a[i]修改为b。至此所有问题都以解决。时间复杂度为 O ( n n ) O(n\sqrt{n}) O(nn )
  • AC代码
#include <bits/stdc++.h>
using namespace std;

#define ll long long 
#define inf 0x3f3f3f3f
#define mes(a, val) memset(a, val, sizeof a)
#define mec(b, a) memcpy(b, a, sizeof a)

const int maxn = 1e5 + 10;
int a[maxn];
int sum[maxn];
int p[30];
int num = 0;
void fac(int n){
    mes(p, 0); num = 0;
    for(int i = 2; i * i <= n; i ++){
        if(n % i == 0){
	    p[++ num] = i;
	    while(n % i == 0) n /= i;
	}
    }
    if(n > 1) p[++ num] = n;
}
int fd(int n){
    return n & 1 ? -1 : 1;
}
int main()
{
    mes(sum, 0);
    int n, q; scanf("%d %d", &n, &q);
    for(int i = 1; i <= n; i ++){
        scanf("%d", &a[i]);
    }
    for(int i = 1; i <= n; i ++){
        for(int j = i; j <= n; j += i){
	    sum[i] += a[j];
	}
    }
    while(q --){
        int op; scanf("%d", &op);
	if(op == 1) {
            int x, d; scanf("%d %d", &x, &d);
	    for(int i = 1; i * i <= x; i ++){
	        if(x % i == 0){
		    sum[i] += (d - a[x]);
		    if(x / i != i) {
		        sum[x/i] += (d - a[x]);
		    }
		}
	    }
	    a[x] = d;
	}
	else if(op == 2) {
	    int x; scanf("%d", &x);
	    fac(x);
	    ll ans = sum[1];
	    int mx = (1 << num) - 1;
	    for(int i = 1; i <= mx; i ++){
	        int cnt = 0;
		int x = i, d = 1;
		int id = 1;
	        for(int j = 1; j <= num; j ++){
		    if(x & 1) {
		        d *= p[j];
                        cnt ++;
		    }
		    x >>= 1;
		}
		ans += fd(cnt) * sum[d];
	    }
	    printf("%lld\n", ans);
	}
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值