Codeforce 920F SUM and REPLACE (类Eratosthenes筛法 线段树)

题目链接有

题意

给出一个运算,将一个正整数,运算后它的值变成它正因数的数量,比如10可以被1,2,5,10整除,那么10经过运算后就会变成4。

现在有一个长度为n的数组a,a中的每个数都不超过1e6,有m个问询,每个问询给出三个变量:t,l,r,当t = 1时,对区间[l, r]的所有数进行上述运算;当t = 2时,输出[l, r]区间内所有数的和。

思路

运算是不能够通过延时标记保存在大区间,进而递归到小区间的,这意味着每一个的区间修改都是由若干个单点修改组成。这时候我们就考虑到,这个运算会不会是数量比较小的,因为直觉上来讲,一个正整数的正因数并不会太多。于是使用类Eratosthenes筛法,计算出小于1e6所有正整数经过运算后的数字,即它们正因数的数量(操作方式见find_val()函数),这个操作的复杂度大概是maxlogmax。然后检查一下每个数最大运算的次数,发现不大于6次。这样我们就可以每次单点修改,维护区间最大值,最大值不大于2的区间我们就可以略过不修改,于是最多修改6*n次,每次logn的时间完成修改。

使用线段树,维护区间之和和区间最大值,实现上述操作即可。

代码

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

const int max_a = 1e6 + 5;
const int max_n = 3e5 + 10;
int divi[max_a];
struct st {
	int l, r;
	ll sum;
	int mx;
}t[max_n << 2];

int n, m;

void build(int id, int l, int r) {
	t[id].l = l, t[id].r = r;
	if (l == r) {
		int x; scanf("%d", &x);
		t[id].sum = t[id].mx = x;
		return;
	}
	int mid = (l + r) >> 1;
	build(id * 2, l, mid);
	build(id * 2 + 1, mid + 1, r);
	t[id].sum = t[id * 2].sum + t[id * 2 + 1].sum;
	t[id].mx = max(t[id * 2].mx, t[id * 2 + 1].mx);

}

ll get_sum(int id, int l, int r) {
	if (l == t[id].l && r == t[id].r) return t[id].sum;
	int mid = (t[id].l + t[id].r) >> 1;
	if (r <= mid) {
		return get_sum(id * 2, l, r);
	}
	else if (l > mid) {
		return get_sum(id * 2 + 1, l, r);
	}
	else {
		return get_sum(id * 2, l, mid) + get_sum(id * 2 + 1, mid + 1, r);
	}
}

void change(int id, int l, int r) {
	// printf("id:%d l:%d r:%d\n", id, l, r);
	if (t[id].l == t[id].r) {
		t[id].mx = t[id].sum = divi[t[id].sum];
		return ;
	}
	if (t[id].mx <= 2)
		return ;
	int mid = (t[id].l + t[id].r) >> 1;
	if (r <= mid) 
		change(id * 2, l, r);
	else if (l > mid)
		change(id * 2 + 1, l, r);
	else {
		change(id * 2, l, mid);
		change(id * 2 + 1, mid + 1, r);
	}
	t[id].sum = t[id * 2].sum + t[id * 2 + 1].sum;
	t[id].mx = max(t[id * 2].mx, t[id * 2 + 1].mx);
}

void find_val() {
	for (register int i = 1; i < max_a; i++) {
		for (register int j = i; j < max_a; j += i) {
			divi[j] ++;
		}
	}
}

int main() {
	find_val();
	scanf("%d %d", &n, &m);
	build(1, 1, n);
	while(m--) {
		int t, l, r;
		scanf("%d %d %d", &t, &l, &r);
		if (t == 1) change(1, l, r); //t = 1是更新
		if (t == 2) printf("%I64d\n", get_sum(1, l, r)); //t = 2是访问
	}
	return 0;
}

总结

因为要复习线段树,畏难心理导致我拖延了很久才补掉这个题。但是其实真正通过看书复习了问询、单点修改这些基本操作之后,再根据思路,大概1个小时就敲出来了,实际code的时间也就十几分钟吧,时间主要花在改自己线段树实现上的bug。

然后顺便看了下延迟更新的操作,也不难,毕竟去年这个时候学的东西。所以特别是自己已经学过的算法,其实是上手很快的,即使现在忘得一干二净,看看书也是会比较快想起来的。下次不要太害怕接触不会敲的算法,因为这么看来,理论上即使比赛上现学也有做出来的机会。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值