牛客小白月赛39D - 绝望(数论 + 数学规律 + 数据结构)

牛客小白月赛39D - 绝望(源地址自⇔牛客


tag

⇔数论、⇔数学规律、⇔数据结构、⇔*1700左右

题意

给定一个长度为 \(N\) 的序列 \(a\) ,规定如下两项操作:

  • 输入 \(4\) 个整数 ” \(1\ L\ R\ X\) “ ,代表将所有 \(i \in [L,R]\) 修改为 \(i^x * a_i\) ,并输出修改后的 \([L,R]\) 区间中的质数数量;
  • 输入 \(3\) 个整数 ” \(2\ L\ R\) “ ,直接输出 \([L,R]\) 区间中的质数数量。

一共 \(Q\) 次询问。

一组样例,满足 \(1≤N≤2∗10 ^5,1≤Q≤5∗10 ^5\) ,且 \(1≤a_i ≤5∗10^5,0≤x≤10\) 。注意,在操作过程中可能出现 \(a_i\ge 5*10^5\)

思路

赛时小结

很显然的线段树题目,而 \(x\) 与质数的规律也很快的找到了,然而交了一发WA之后重新推导才发现漏掉了一些特殊情况……

正解

首先,很显然需要用到线段树和素数筛,原因略。

我们分析题意,可以归纳出以下几点特征:

  • 对于 \(i=1\) ,无论如何操作,不会改变原来数的特征;
  • 对于 \(i\) 为质数且 \(a_i=1,x=1\) 的情况,会将其改变成质数;
  • 对于 \(x=0\) 的情况,不会改变原有特征;
  • 其余所有情况,都会将区间内的质数变成合数。

依据前三条特征,容易发现需要对单点进行操作,故我们需要使用化区间修改为单点修改的思想。在修改操作中做出相应的特判:

  • 对于第一、三条特征,直接忽视;
  • 对于第二条特征,使用额外的 \(\tt{}mp\) 数组记录该点 \(i\) 是否为质数,使用额外的 \(\tt{}one\) 数组记录该点的值是否为 \(1\)

对于第四条特征,我们使用区间修改思想,直接将该区间的值置为 \(0\) ,并将额外数组 \(\tt{}one\) 置为 \(0\)

由于本题第四条特征占了绝对多数:可以计算,一个数至多经过两次操作就会变成合数,考虑最坏情况, \(N\) 个数字均需要处理,那么此时总单点操作的完整复杂度为 \(\mathcal{O}(2 * N*logN+Q*logN)\) ,平均时间复杂度即为 \(\mathcal{O}(Q*logN)\) ,所以时间复杂度可以通过。

AC代码

点击查看代码
//====================
#define int LL
const int N	= 1e6 + 7;
#define lk k << 1
#define rk k << 1 | 1
struct node{
	int l, r, w, one;
}tre[4 * N];
int n, q;
//====================
vector<int> prime;
map<int, int> mp; int v[N];
void Force() {
	int n = N - 5;
	for(int i = 2; i <= n; i ++) {
        if(v[i] == 0) {
            v[i] = i;
            prime.push_back(i);  
        }
        for (auto it : prime) {
        	if (it > v[i] || it > n / i) break;
        	v[i * it] = it;
        }
    }
    for (auto it : prime) mp[it] = 1;
}
void update(int k) {
	tre[k].w = tre[lk].w + tre[rk].w;
	tre[k].one = tre[lk].one | tre[rk].one;
}
void build(int l, int r, int k) {
	tre[k].l = l, tre[k].r = r;
	if (l == r) {
		int x; cin >> x;
		if (x == 1) tre[k].one = 1;
		tre[k].w = mp[x];
		return;
	}
	int m = (tre[k].l + tre[k].r) >> 1;
	build(l, m, lk), build(m + 1, r, rk);
	update(k);
}
int finds(int l, int r, int k) {
	if (l <= tre[k].l && tre[k].r <= r) return tre[k].w;
	int ans = 0;
	int m = (tre[k].l + tre[k].r) >> 1;
	if (l <= m) ans += finds(l, r, lk);
	if (m < r) ans += finds(l, r, rk);
	return ans;
}
void change_point(int l, int r, int x, int k) {
	if (x == 0) return;
	if (tre[k].w == 0 && tre[k].one == 0) return;
	if (tre[k].l == tre[k].r) { //单点修改
		int num = tre[k].l;
		if (num == 1) return;
		if (x == 1 && tre[k].one == 1 && mp[num] == 1) {
			tre[k].one = 0;
			tre[k].w = 1;
			return;
		}
		tre[k].one = tre[k].w = 0;
		return;
	}
	int m = (tre[k].l + tre[k].r) >> 1;
	if (l <= m) change_point(l, r, x, lk);
	if (m < r) change_point(l, r, x, rk);
	update(k);
}
void Solve() {
	cin >> n >> q;
	build(1, n, 1);
	for (int i = 1; i <= q; ++ i) {
		int op; cin >> op;
		if (op == 2) {
			int l, r; cin >> l >> r;
			cout << finds(l, r, 1) << endl;
		}else {
			int l, r, x; cin >> l >> r >> x;
			change_point(l, r, x, 1);
			cout << finds(l, r, 1) << endl;
		}
	}
}

错误次数

(赛时 3 次)未考虑全所有情况。


文 / WIDA
2022.03.27 成文
首发于WIDA个人博客,仅供学习讨论


posted @ 2022-03-27 14:23  hh2048  阅读(30)  评论(0编辑  收藏  举报