「 Luogu P2801 」 教主的魔法——分块
# 解题思路
修改,就是一个区间修改的常规操作,但是为了迎合查询的需要,对两端的不完整的块需要暴力重构,重新进行排序操作,保证每一块都是单调上升的顺序。
然后再说进行查询的操作,起初,我们需要在每一个块内进行排序。保证顺序时单调上升的(在每一个块内,是独立的),然后查询的时候对每一块($k$)都二分查找到大于 $num + tag[k]$ 的第一个数的位置,然后就可以得到一共有多少大于 $num$ 的数了。
值得注意的是,有些题解写的是对原序列直接进行排序,这就有一个错误,那就是在排序之后序列的顺序改变,改变了之后在进行区间修改的时候就不能保证正确性了。
所以我们开一个 vector 将每个块内的数都存下来。我的代码 T 了一个点,但是了 O2 过了,懒得再去卡常了
# 附上代码
#include#include #include #include #include #include using namespace std; const int maxn = 1e6+3; int N, Q, arr[maxn], in[maxn], cnt, tag[maxn]; vector<int> b[1003]; struct BLOCK { template inline void read(T &x) { x = 0; T f = 1; char c = getchar(); while (c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();} while (c <= '9' && c >= '0') {x = x*10 + c-'0'; c = getchar();} x *= f; } void reset(int k) { b[k].clear(); for(int i=(k-1)*cnt+1; i<=min(k*cnt, N); i++) arr[i] += tag[k], b[k].push_back(arr[i]); sort(b[k].begin(), b[k].end()); tag[k] = 0; } void build() { cnt = sqrt(N); for(int i=1; i<=N; i++) { read(arr[i]); in[i] = (i-1)/cnt+1; b[in[i]].push_back(arr[i]); } for(int i=in[1]; i<=in[N]; i++) sort(b[i].begin(), b[i].end()); } void update(int l, int r, int num) { for(int i=l; i<=min(in[l]*cnt, r); i++) arr[i] += num; reset(in[l]); if(in[l] != in[r]) { for(int i=(in[r]-1)*cnt+1; i<=r; i++) arr[i] += num; reset(in[r]); } for(int i=in[l]+1; i<in[r]; i++) tag[i] += num; } int check(int k, int num) { int ans = (b[k].end()-lower_bound(b[k].begin(), b[k].end(), num-tag[k])); return ans; } int query(int l, int r, int num) { int ans = 0; for(int i=l; i<=min(in[l]*cnt, r); i++) if(arr[i] + tag[in[i]] >= num) ans ++; if(in[l] != in[r]) { for(int i=(in[r]-1)*cnt+1; i<=r; i++) if(arr[i] + tag[in[i]] >= num) ans ++; } for(int i=in[l]+1; i<in[r]; i++) ans += check(i, num); return ans; } BLOCK () { read(N), read(Q); build(); char opt; int l, r, num; for(int i=1; i<=Q; i++) { cin>>opt; read(l), read(r), read(num); if(opt == 'M') update(l, r, num); else printf("%d\n", query(l, r, num)); } } }BLO; int main() {}