Luogu P2801 教主的魔法 题解

前言

题目传送门 Luogu P2801 教主的魔法

好题,将分块的精髓------整块维护,局部暴力------体现得淋漓尽致。

题意

给定 \(n\) 个数,实现以下两种操作:

  • M L R W ,表示将 \(L,R\) 内每个数加上 \(W\) 。

  • A L R C ,表示询问 \(L,R\) 内有多少个数 \(\ge C\) 。

对于 \(100\%\) 的数据,\(N≤10^6\),\(Q≤3000\),\(1≤W≤1000\),\(1≤C≤10^9\)。

思路

首先考虑朴素的分块。容易想到以下方法:

  1. 读取数列 \(a_n\) ,分为 \(\sqrt{n}\) 块;

  2. 区间修改。我们维护增量标记 add[] 来加速整块修改;碎块暴力修改。

  3. 区间查询。要查询区间内 \(\ge c\) 的数有多少,容易想到如下思路:对于整块我们直接二分,对于碎块暴力枚举。可是我们无法保证块内的数有序,如果每次查询都排序,复杂度还不如直接枚举。考虑找一种办法能让我们不用重复排序。

这时,优化方法也就显而易见了。注意到,如果我们对一个区间加上同一个数,并不会改变其中各数的大小关系。所以我们考虑维护每个整块内部始终有序性价比较高。

考虑如下的做法:

  1. 定义辅助数组 b[] ,初始时与 a[] 相同。预处理时,我们在 b[] 中对所有整块分别排序。这样,a[]b[] 中的块仍然一一对应,而 b 中每块都有序。复杂度 \(O(n\log n)\) 。

  2. 考虑区间修改。思路基本同上:对于整块,我们只维护增量,而不重新排序;对于碎块,我们暴力修改,改完后把 a[] 中的这一碎块所在整块复制到 b[] 上,并在 b[] 上重新排序。复杂度 \(O(\sqrt{n}+\sqrt{n}\log\sqrt{n})\) 。

  3. 考虑区间查询。对于整块,因为保证块内有序,我们直接在这块内二分查找;对于碎块,直接暴力枚举。复杂度 \(O(\sqrt{n}\log\sqrt{n}+\sqrt{n})\) 。

综上,总复杂度约为 \(O(n\log n+m\sqrt{n}\log\sqrt{n}+m\sqrt{n})\) 。极限数据下运算量在 \(10^7\) 量级,可以通过此题。

代码

cpp 复制代码
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#define fastio ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define MIKU 0;
using namespace std;

int n, q, a[1000005], b[1000005];

namespace blc {
	int siz, cnt;
	int op[1005], ed[1005], pos[1000005], add[1005];
	void init() {
		siz = sqrt(n);
		cnt = n % siz ? n / siz + 1 : n / siz;
		for(int i=1; i<=cnt; i++) {
			op[i] = (i - 1) * siz + 1;
			ed[i] = i * siz;
		}
		ed[cnt] = n;
		for(int i=1; i<=n; i++) pos[i] = (i - 1) / siz + 1;
		memcpy(b, a, sizeof(a));
		for(int i=1; i<=cnt; i++) sort(b+op[i], b+ed[i]+1);
	}
	void update(int l, int r, int w) {
		int L = pos[l], R = pos[r];
		if(L == R) {
			for(int i=l; i<=r; i++) a[i] += w;
			memcpy(b+op[L], a+op[L], siz * 4);
			sort(b+op[L], b+ed[L]+1);
		} else {
			for(int i=L+1; i<=R-1; i++) add[i] += w;
			for(int i=l; i<=ed[L]; i++) a[i] += w;
			memcpy(b+op[L], a+op[L], siz * 4);
			sort(b+op[L], b+ed[L]+1);
			for(int i=op[R]; i<=r; i++) a[i] += w;
			memcpy(b+op[R], a+op[R], siz * 4);
			sort(b+op[R], b+ed[R]+1);
		}
	}
	int query(int l, int r, int c, int res = 0) {
		int L = pos[l], R = pos[r];
		if(L == R) {
			for(int i=l; i<=r; i++) if(a[i] + add[L] >= c) res ++;
		} else {
			for(int i=L+1; i<=R-1; i++) {
				int p = lower_bound(b+op[i], b+ed[i]+1, c-add[i]) - b - op[i] + 1;
				res += siz - p + 1;
			}
			for(int i=l; i<=ed[L]; i++) if(a[i] + add[L] >= c) res ++;
			for(int i=op[R]; i<=r; i++) if(a[i] + add[R] >= c) res ++;
		}
		return res;
	}
}

int main() {
	fastio;
	cin>>n>>q;
	for(int i=1; i<=n; i++) cin>>a[i];
	blc::init();
	while(q--) {
		char op; cin>>op;
		if(op == 'M') {
			int l, r, w; cin>>l>>r>>w;
			blc::update(l, r, w);
		} else {
			int l, r, c; cin>>l>>r>>c;
			cout<<blc::query(l, r, c)<<'\n';
		}
	}
	return MIKU;
}