Solution
感觉这题的思路有点神。(有种树套树的赶脚)
首先我们可以开一颗左偏树维护每个连通块的最大值,再开一颗左偏树来维护每个连通块的最大值的最大值(就是将每个连通块的最大值代表的节点再建一颗左偏树)。
这样我们就可以解决第七个问题了。
详见注释。
P.S. 感觉代码的时间复杂度很不 ok 呢,并查集没有用路径压缩,最坏情况是 (mathtt{O(n)}) 级别的复杂度啊???还有常数似乎有点大???反正 (mathtt{O(玄学)}) 但 (mathtt{O(可过)}) 就对了。。。
Code
#include <queue>
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 3e5 + 5;
int n;
queue <int> q;
int read() {
int x = 0, f = 1; char s;
while((s = getchar()) < '0' || s > '9') if(s == '-') f = -1;
while(s >= '0' && s <= '9') {x = (x << 1) + (x << 3) + (s ^ 48); s = getchar();}
return x * f;
}
struct LT {
int dis[N], val[N], la[N], f[N], son[N][2];
void sign(const int o, const int Add) {
if(! o) return;
val[o] += Add; la[o] += Add;
}
int Find(int x) {
while(f[x]) x = f[x];
return x;
}
void pushDown(const int o) {
if(! o) return;
sign(son[o][0], la[o]);
sign(son[o][1], la[o]);
la[o] = 0;
}
int unite(int x, int y) {
if(! x || ! y) return x | y;
if(val[x] < val[y]) swap(x, y);
pushDown(x);
son[x][1] = unite(son[x][1], y); f[son[x][1]] = x;
if(dis[son[x][0]] < dis[son[x][1]]) swap(son[x][0], son[x][1]);
dis[x] = dis[son[x][1]] + 1;
return x;
}
void Clear(const int x) {f[x] = son[x][0] = son[x][1] = 0;}
int get(int x) {
int ans = val[x];
for(x = f[x]; x; x = f[x]) ans += la[x];
return ans;
}
int del(const int x) {
pushDown(x);
int t = unite(son[x][0], son[x][1]), fa = f[x];
f[t] = fa;
if(fa) son[fa][x == son[fa][1]] = t;
Clear(x);
while(fa) {//维护树的形态
if(dis[son[fa][0]] < dis[son[fa][1]]) swap(son[fa][0], son[fa][1]);
if(dis[fa] == dis[son[fa][1]] + 1) return Find(fa);
dis[fa] = dis[son[fa][1]] + 1;
t = fa; fa = f[fa];
}
return t;
}
int add(const int u, const int k) {
val[u] += k;
pushDown(f[u]); pushDown(u);//传递父亲的权值就可以比较父亲和自己,传递自己的权值可以比较自己和儿子
if(val[f[u]] >= val[u] && val[u] >= val[son[u][0]] && val[u] >= val[son[u][1]]) return Find(u);//树的形态没有变
val[u] = get(u);
return unite(del(u), u);//先删再合并
}
int build() {
for(int i = q.size(), j, k; i > 1; -- i) {
j = q.front(), q.pop();
k = q.front(), q.pop();
q.push(unite(j, k));
}
return q.front();//build 可以感性理解:尽量将点数差不多的合并在一起,可以使树长得没那么瘦。。。
}
}T1, T2;
int main() {
int n = read(), x, y, a, b, root, tot = 0; char op[10];
for(int i = 1; i <= n; ++ i) q.push(i), T1.val[i] = T2.val[i] = read();
root = T2.build();//初始每个点都是一棵树
int q = read();
while(q --) {
scanf("%s", op);
if(op[0] == 'U') {
x = T1.Find(read()), y = T1.Find(read());
if(x == y) continue;
root = T2.del(T1.unite(x, y) == x ? y : x);//合并两个点时,其在树2就必须少一个点
}
else if(op[0] == 'A' && op[1] == '1') {
x = read(), y = read();
a = T1.Find(x), b = T1.add(x, y);
if(a ^ b) {//说明 x 成为连通块的最大值,我们就更改在树2的位置与权值
root = T2.del(a);
T2.val[b] = T1.val[b];
root = T2.unite(root, b);
}
else if(x == a) root = T2.add(x, y);
}
else if(op[0] == 'A' && op[1] == '2') {
x = T1.Find(read()), y = read();
T1.val[x] += y, T1.la[x] += y;
root = T2.add(x, y);
}
else if(op[0] == 'A' && op[1] == '3') {
tot += read();
}
else if(op[0] == 'F' && op[1] == '1') {
printf("%d
", T1.get(read()) + tot);
}
else if(op[0] == 'F' && op[1] == '2') {
printf("%d
", T1.val[T1.Find(read())] + tot);
}
else {
printf("%d
", T2.val[root] + tot);
}
}
return 0;
}