[BZOJ4631]踩气球
[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; }