[BZOJ4631]踩气球
试题描述
输入
输出
输入示例
5 3 1 1 1 1 1 5 5 2 2 1 3 5 4 2 5 2 3
输出示例
0 1 1 2 3
数据规模及约定
见“输入”
题解
解法1
考虑预处理所有熊孩子。把第 i 个熊孩子想象成一个坐标为 (Li, Ri) 的点,点权为 A 数组 Li 到 Ri 的连续和。那么对于一个操作 x(把 x 号箱子中的一个气球踩爆),就是平面上 (x, x) 这个点的左上区域所有点权值减 1. 于是可以用 kd 树维护,支持区间减 1 操作,每次减 1 操作结束后看最小值是否减到 0,若减到 0,暴力将所有权值减到零的点删除,所以 kd 树还需要维护最小权值和最小权值所在位置。注意这里每个点最多被删除一次,每次删除复杂度为 O(logn),所以总时间复杂度为 O(n·sqrt(n) + n·logn)。
我写了一发,然而 T 飞了。。。
解法2
我们对原序列 A 进行操作,每次将某个位置的值 Ai 减 1. 考虑使用并查集维护当前最大的区间 [L, R] 使其满足区间内所有数都为 0,对于一个操作 x,若位置 x 的值被减到了 0,则和它左右两个相邻的减到 0 的位置合并,得到这个最大的区间 [L, R]。这次我们仍然把所有的熊孩子看成一个点加入到 kd 树中,但点权初始都为 1. 这样对于一个所有数都为 0 的区间 [L, R],在平面内找到 (L, R) 这个点,则需要统计的就是 [L, R] 包含的所有熊孩子区间,在平面上就是 (L, R) 右下方的点的个数,注意判重,处理方法:统计完区域的点数(即所有点权值和)后,将该区域所有点权赋 0. kd 树 O(n·sqrt(n)),并查集 O(n·logn)(我只写了路径压缩)。
这个算法跑得飞快。。。
结论:kd 树在涉及到区域操作时,复杂度为 O(玄学)。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <queue>
#include <cstring>
#include <string>
#include <map>
#include <set>
using namespace std;
const int BufferSize = 1 << 16;
char buffer[BufferSize], *Head, *Tail;
inline char Getchar() {
if(Head == Tail) {
int l = fread(buffer, 1, BufferSize, stdin);
Tail = (Head = buffer) + l;
}
return *Head++;
}
int read() {
int x = 0, f = 1; char c = Getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = Getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = Getchar(); }
return x * f;
}
#define maxn 100010
#define oo 2147483647
#define LL long long
int n, m, lc[maxn], rc[maxn], Cur, A[maxn], root, ans;
int fa[maxn], mn[maxn], mx[maxn];
int findset(int x){ return x == fa[x] ? x : fa[x] = findset(fa[x]); }
struct Node {
int x[2], mx[2], mn[2], setv, sumv, has;
bool operator < (const Node& t) const { return x[Cur] < t.x[Cur]; }
} ns[maxn], x;
void maintain(int o) {
int l = lc[o], r = rc[o];
for(int i = 0; i < 2; i++) {
ns[o].mx[i] = max(max(ns[l].mx[i], ns[r].mx[i]), ns[o].x[i]);
ns[o].mn[i] = min(min(ns[l].mn[i], ns[r].mn[i]), ns[o].x[i]);
}
ns[o].sumv = ns[l].sumv + ns[r].sumv + ns[o].has;
return ;
}
void build(int& o, int L, int R, int cur) {
if(L > R){ o = 0; return ; }
int M = L + R >> 1; o = M;
Cur = cur; nth_element(ns + L, ns + M, ns + R + 1);
build(lc[o], L, M - 1, cur ^ 1); build(rc[o], M + 1, R, cur ^ 1);
maintain(o);
return ;
}
void pushdown(int o) {
int l = lc[o], r = rc[o];
if(ns[o].setv >= 0) {
ns[o].sumv = ns[o].has = 0;
ns[l].setv = ns[r].setv = 0;
ns[o].setv = -1;
}
return ;
}
bool all(int o) { return ns[o].mn[0] >= x.x[0] && ns[o].mx[1] <= x.x[1]; }
bool has(int o) { return ns[o].mx[0] >= x.x[0] && ns[o].mn[1] <= x.x[1]; }
void del(int o) {
if(!o) return ;
pushdown(o);
int l = lc[o], r = rc[o];
if(all(l)){ ns[l].setv = 0; ans += ns[l].sumv; ns[l].sumv = ns[l].has = 0; }
else if(has(l)) del(l);
if(all(r)){ ns[r].setv = 0; ans += ns[r].sumv; ns[r].sumv = ns[r].has = 0; }
else if(has(r)) del(r);
if(ns[o].x[0] >= x.x[0] && ns[o].x[1] <= x.x[1]){ ans += ns[o].has; ns[o].has = 0; }
maintain(o);
return ;
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ns[0].mx[0] = ns[0].mx[1] = -oo;
ns[0].mn[0] = ns[0].mn[1] = oo;
n = read(); m = read();
for(int i = 1; i <= n; i++) A[i] = read();
for(int i = 1; i <= m; i++) {
int L = read(), R = read();
ns[i].x[0] = L; ns[i].x[1] = R;
ns[i].setv = -1;
ns[i].has = 1;
}
build(root, 1, m, 0);
int q = read();
for(int i = 1; i <= n; i++) fa[i] = mx[i] = mn[i] = i;
while(q--) {
// int p = read();
int p = (read() + ans - 1) % n + 1;
// printf("p: %d\n", p);
if(!(--A[p])) {
int u, v;
if(p && !A[p-1]) {
u = findset(p-1); v = findset(p);
if(u != v) fa[v] = u, mn[u] = min(mn[u], mn[v]), mx[u] = max(mx[u], mx[v]);
}
if(p < n && !A[p+1]) {
u = findset(p+1); v = findset(p);
if(u != v) fa[v] = u, mn[u] = min(mn[u], mn[v]), mx[u] = max(mx[u], mx[v]);
}
u = findset(p);
x.x[0] = mn[u]; x.x[1] = mx[u];
// printf("%d %d\n", mn[u], mx[u]);
del(root);
}
printf("%d\n", ans);
}
return 0;
}