线段树合并

前置芝士 —— 动态开点

什么是动态开点,是用于处理一些区间跨度比较大,空间比较小的题目。

比如:

\(1 100000\) 建图,那就和 \(1 2 3 …… 10000\) 一样的内存开销。

肯定是不可以直接建,那样空间会炸。

所以有 \(2\) 中办法:

\(1.\) 离散化

这个办法是很早就开始用了的,也比较有效。

\(2.\) 动态开点

当你遇到题目要求强制在线的时候怎么办呢?

就不能离散化了啊。

这就开始动态开点了。。。

首先,在原问题中,你需要解决的问题是:

内存不足并且有很多的无用节点。

那你就不给他内存就行了啊

你就不一定想平常那样子 \(x << 1\)\(x << 1 | 1\) 的建树

把每个需要用的节点的左右节点都用数组记录下来。

其他不需要用的节点就不给他分配内存。

具体的话,可以看看这段伪代码代码:

int Build(int l, int r) { int now = inc(rt);
    if(l < r) { L[now] = Build(l, mid), R[now] = Build(mid + 1, r);}
    return now;
}
int Updata(int pre, int l, int r, int x) { int now = inc(rt);
    L[now] = L[pre], R[now] = R[pre], Data[now] = Data[pre] + 1;
	if(l < r) {if(x <= mid) L[now] = Updata(L[pre], l, mid, x);
    else R[now] = Updata(R[pre], mid + 1, r, x);}
    return now;
}
int main() {
    T[0] = build(1, m);
    Rep(i, 1, n) T[i] = updata(T[i - 1], 1, m, a[i]);
    ………………
    return 0;
}

回归正题

当你有 \(2\) 颗线段树,你需要维护的东西包含了这 \(2\) 颗线段树。

必然区间内的数出现个数等等,你不可以之间从 \(2\) 颗树种分别求解。

那样子就显然会错,这个时候你可以把 \(2\) 颗线段树合并在一起。

求合并以后的答案就行了。

那么线段树合并的规则是什么呢?

因为最后 \(2\) 颗线段树只会留一颗。

所以不妨设 \(a\) 为主树, \(b\) 为需要合并进去的树。

那么对于每个节点分以下几种情况讨论:

\(1.\) 如果只有 \(a\) 上有这个节点,那么就不用管,直接返回 \(a\) 就行了,也可以不用管,特判掉。因为合并后的树的原树是 \(a\)

\(2.\) 如果只有 \(b\) 上有就返回 \(b\)就行了。

\(3.\) 如果 \(a\)\(b\) 都有话就只要直接按要求合并就行,即 \(a\)\(b\) 做一些运算。

\(4.\) 像线段树递归一样的递归就行了。

\(5.\) 还有就是,必须要动态开点。

放一段伪代码:


void work() {
    a += …………;
}

int merge(int x, int y, int l, int r) {
    if(x == 0) return y;
    if(y == 0) return x;
    if(l == r) {
        work(); return a;
    } int mid = l + r >> 1;
    e[x].l = merge(e[x].l, e[y].l, l, mid);
    e[x].r = merge(e[x].r, e[y].r, mid + 1, r);
    push_up(a); return a;
}

看上去好简单粗暴,那么它的复杂度是什么呢?

时间复杂度和普通的线段树还是一样的,因为线段树的时间复杂度已经很优秀了。

主要是空间复杂度,普通线段树需要开 \(4 * n\) 的线段树。

如果 \(q\) 很小,但是值域很大的话,空间显然会炸。

从根节点到叶节点高度为 \(log_n\) 的,一共 \(O(q * log_n)\) 的。

大概可以稳稳(不是)的通过插入次数在 \(10^5\) ,值域在 \(10^5\) 的题目。

线段树合并的作用:

可以干一些权值线段树可以干的,

比如维护 \(n\) 颗线段树的第 \(k\) 大,等等。

update:

在做完 \(4\) 题以后终于悟了一点什么了。。

线段树合并本质上是一种优化,他和线段树本身就是一样的。

线段树也只是把区间上的 \(O(n)\) 的作法搬到树上,通过树的树高为 \(log\) 等特性强行把复杂度优化到 \(log\)

而线段树合并与暴力合并之间的区别也就是在区间上线性的做与在线段树上 \(log\) 的做的本质是一样的。

例题选讲

CF600E

题目大意

一棵树有 \(n\) 个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和。

题目分析

首先考虑暴力,对于每颗子树都全部重新加进一颗线段树。

复杂度是 \(O(树高 * nlog_n)\)

而题目的数据范围卡在了 \(10^5\)

这也是我们前面所说的线段树合并可以跑的极限了。

而这个树高,就算不造数据恶意取卡,也至少是 \(log_n\) 的复杂度。

显然是不能接受的。

接下来就是线段树出场了。

假设你现在在节点 \(u\) ,那么你只需要把 \(u\) 的所有子节点为根的子树都合并起来,可以证明每个节点都只会合并一次。

复杂度也就是 \(O(nlog_n)\) 的。

代码

注意:本题挂了 \(3\)

第一次,数组开小了

第二次,在卡内存的情况下使用 \(define int long long\) 炸空间了

第三次,改了局部 \(long long\) 但是输出没加 \(\%lld\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#define maxn 100010
#define maxm 10000010
#define ls x << 1
#define rs x << 1 | 1
#define inf 10000000000
#define inc(i) (++ (i))
#define dec(i) (-- (i))
#define mid ((l + r) >> 1)
//#define int long long
#define XRZ 212370440130137957
#define debug() puts("XRZ TXDY");
#define mem(i, x) memset(i, x, sizeof(i));
#define Next(i, u) for(register int i = head[u]; i ; i = e[i].nxt)
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout);
#define Rep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i <= i##Limit ; inc(i))
#define Dep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i >= i##Limit ; dec(i))
int dx[10] = {1, -1, 0, 0};
int dy[10] = {0, 0, 1, -1};
using namespace std;
inline int read() {
    register int x = 0, f = 1; register char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
    return x * f;
}
int col[maxn], n, cnt, tot, T[maxn], head[maxn << 1]; long long  Ans[maxn];
struct edge { int nxt, to;} e[maxn << 1];
struct node { int l, r, flag; long long num;} tr[maxm];
void add(int u, int v) { e[inc(cnt)] = (edge) {head[u], v}; head[u] = cnt;}
void pushup(int x, int l, int r) {
	if(tr[l].flag > tr[r].flag) tr[x].flag = tr[l].flag, tr[x].num = tr[l].num;
	else if(tr[l].flag < tr[r].flag) tr[x].flag = tr[r].flag, tr[x].num = tr[r].num;
	else tr[x].flag = tr[l].flag, tr[x].num = tr[r].num + tr[l].num;
}
int merge(int x, int y, int l, int r) {// debug()
	if(x == 0 || y == 0) return x + y;
	if(l == r) { tr[x].flag += tr[y].flag; return x;}
	tr[x].l = merge(tr[x].l, tr[y].l, l, mid);
	tr[x].r = merge(tr[x].r, tr[y].r, mid + 1, r);
	pushup(x, tr[x].l, tr[x].r); return x;
}
void Add(int k, int l, int r, int x) {
	if(l == r) { inc(tr[x].flag); tr[x].num = l; return ;}
	if(k <= mid) {
		if(tr[x].l == 0) { tr[x].l = inc(tot);}
		Add(k, l, mid, tr[x].l);
	} else {
		if(tr[x].r == 0) { tr[x].r = inc(tot);}
		Add(k, mid + 1, r, tr[x].r);
	} pushup(x, tr[x].l, tr[x].r);
}
void Dfs(int u, int fa) { //debug()
	Next(i, u) { int v = e[i].to;
		if(v != fa) { Dfs(v, u);
			T[u] = merge(T[u], T[v], 1, n);
		}
	} if(! T[u]) { T[u] = inc(tot);}
	Add(col[u], 1, n, T[u]); Ans[u] = tr[T[u]].num;
}
signed main() { n = read();
	Rep(i, 1, n) col[i] = read();
	Rep(i, 1, n - 1) { int u = read(), v = read(); add(u, v), add(v, u);} //debug()
	Dfs(1, 0); Rep(i, 1, n) printf("%lld ", Ans[i]);
	return 0;
}

[Vani有约会]雨天的尾巴

题目大意

给你一棵树,每次会给一条路径 \((x, y)\) 发放 \(c_i\) 种原料。

问每颗子树中的个数最多的原料是什么。

题解

也比较好想吧,对于每个子树都维护原料的信息,然后在求区间的时候运用树上差分,就可以辣。

不过好难写,不但要写线段树合并,还要写LCA等等

代码

先放上一个 没过样例 的代码,可以看思路。

现在过了。


#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#define maxn 100010
#define maxm 10000010
#define ls x << 1
#define rs x << 1 | 1
#define inf 100000
#define inc(i) (++ (i))
#define dec(i) (-- (i))
#define mid ((l + r) >> 1)
//#define int long long
#define XRZ 212370440130137957
#define debug() puts("XRZ TXDY");
#define mem(i, x) memset(i, x, sizeof(i));
#define Next(i, u) for(register int i = head[u]; i ; i = e[i].nxt)
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout);
#define Rep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i <= i##Limit ; inc(i))
#define Dep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i >= i##Limit ; dec(i))
int dx[10] = {1, -1, 0, 0};
int dy[10] = {0, 0, 1, -1};
using namespace std;
inline int read() {
    register int x = 0, f = 1; register char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
    return x * f;
} int head[maxn << 1], ans[maxn], T[maxn], cnt, tot, dep[maxn], fa[maxn][20]; //fa记录每个节点的父亲 
struct edge { int to, nxt;} e[maxn << 1];
struct node { int l, r, sum, flag;} tr[maxm];
void Add(int u, int v) { e[inc(cnt)] = (edge) {v, head[u]}; head[u] = cnt;}
void pushup(int x) {
	if(! tr[x].l) tr[x].sum = tr[tr[x].r].sum, tr[x].flag = tr[tr[x].r].flag;
	else if(! tr[x].r) tr[x].sum = tr[tr[x].l].sum, tr[x].flag = tr[tr[x].l].flag;
	else if(tr[tr[x].l].sum >= tr[tr[x].r].sum) tr[x].sum = tr[tr[x].l].sum, tr[x].flag = tr[tr[x].l].flag;
	else tr[x].sum = tr[tr[x].r].sum, tr[x].flag = tr[tr[x].r].flag;
}
int update(int x, int l, int r, int pos, int fla) {
	if(! x) x = inc(tot);
	if(l == r) { tr[x].sum += fla; tr[x].flag = l; return x;}
	if(pos <= mid) tr[x].l = update(tr[x].l, l, mid, pos, fla);
	else tr[x].r = update(tr[x].r, mid + 1, r, pos, fla);
	pushup(x); return x;
}
int merge(int x, int y, int l, int r) {
	if(! x || ! y) return x + y;
	if(l == r) { tr[x].sum += tr[y].sum; return x;}
	tr[x].l = merge(tr[x].l, tr[y].l, l, mid);
	tr[x].r = merge(tr[x].r, tr[y].r, mid + 1, r);
	pushup(x); return x;
}
void Tarjin(int u, int F) {
	fa[u][0] = F; dep[u] = dep[F] + 1;
	Rep(i, 1, 18) fa[u][i] = fa[fa[u][i - 1]][i - 1];
	Next(i, u) { int v = e[i].to;
		if(v != F) Tarjin(v, u);
	}
}
int lca(int x, int y) { if(dep[x] < dep[y]) swap(x, y);
	Dep(i, 18, 0) if(dep[fa[x][i]] >= dep[y]) x = fa[x][i];
	if(x == y) return x;
	Dep(i, 18, 0) if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
	return fa[x][0];
}
void Dfs(int x, int F) {
	Next(i, x) { int v = e[i].to;
		if(v == F) continue; Dfs(v, x);
		T[x] = merge(T[x], T[v], 1, inf);
	} ans[x] = tr[T[x]].flag;
	if(tr[T[x]].sum == 0) ans[x] = 0;
} 
signed main() { int n = read(), m = read();
	Rep(i, 1, n - 1) { int x = read(), y = read(); Add(x, y), Add(y, x); }
	Tarjin(1, 0);
	Rep(i, 1, m) { int l = read(), r = read(), x = read(); int F = lca(l, r);
		T[l] = update(T[l], 1, inf, x, 1);
		T[r] = update(T[r], 1, inf, x, 1);
		T[F] = update(T[F], 1, inf, x, -1);
		T[fa[F][0]] = update(T[fa[F][0]], 1, inf, x, -1);
	}//cout<<"debug1"<<endl; 
	Dfs(1, 0); Rep(i, 1, n) printf("%lld\n", ans[i]);
	return 0;
}

[HEOI2016 TJOI2016]排序

题目大意

给你一个长度为 \(n\) 的区间,你会继续 \(m\) 次排序。

每次排序会给定 \(0/1 l r\)

如果为 \(0\) 表示,把 \([l, r]\) 升序排序。

如果为 \(1\) 则表示,把 \([l, r]\) 降序排序。

问你 \(m\) 次排序以后,第 \(Q\) 位是什么。

题解

虽然这道题是个线段树合并的例题

但是我觉得不需要线段树合并,只要用线段树就够了。

据说要用线段树分裂 \((?)\) 但是不会/kk

我就讲下线段树做法吧:

你可以转化下题意,

每次排序其实就相当于把一些数放到了前面,其他放到了后面。

因为你只要求第 \(Q\) 位,并且题目也没有强制在线。

那么你就可以把他离线以后维护一颗区间加的线段树。

二分数值 \(x\) ,并且对于每个数,如果大于 \(x\) 就赋值为 \(1\) 不然就赋值为 \(0\) 做区间加就可以找到有多少大于他的数。二分到最后就是答案。

代码

目前只有 \(60ps\) 其余的点都 \(RE\)

现在过了, \(RE\) 的原因是有数据修改的时候 \(l > r\) 没判所以 \(RE\) 了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#define maxn 1000010
#define maxm 1000010
#define ls x << 1
#define rs x << 1 | 1
#define inf 10000000000
#define inc(i) (++ (i))
#define dec(i) (-- (i))
#define mid ((l + r) >> 1)
#define int long long
#define XRZ 212370440130137957
#define debug() puts("XRZ TXDY");
#define mem(i, x) memset(i, x, sizeof(i));
#define Next(i, u) for(register int i = head[u]; i ; i = e[i].nxt)
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout);
#define Rep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i <= i##Limit ; inc(i))
#define Dep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i >= i##Limit ; dec(i))
int dx[10] = {1, -1, 0, 0};
int dy[10] = {0, 0, 1, -1};
using namespace std;
inline int read() {
    register int x = 0, f = 1; register char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
    return x * f;
} int a[maxn], Q, x, y, n, m, ans, tr[maxn << 2], fla[maxn << 2], opt[maxn], ll[maxn], rr[maxn];
bool vis[maxn];
void lazy(int x, int l, int r, int k) { tr[x] = (r - l + 1) * k, fla[x] = k;}
void pushdown(int x, int l, int r) {
    if(~fla[x]) {
        lazy(ls, l, mid, fla[x]);
        lazy(rs, mid + 1, r, fla[x]);
        fla[x] = -1;
    }
}
void Build(int x, int l, int r) {
    if(l == r) { tr[x] = vis[l]; return;}
    Build(ls, l, mid), Build(rs, mid + 1, r);
    tr[x] = tr[ls] + tr[rs];
}
void Modify(int L, int R, int l, int r, int x, int k) {
    if(L > R) return;
    if(L <= l && r <= R) {
        tr[x] = (r - l + 1) * k;
        fla[x] = k; return ; 
    } pushdown(x, l, r);
    if(L <= mid) Modify(L, R, l, mid, x << 1, k);
    if(R > mid) Modify(L, R, mid + 1, r, x << 1 | 1, k);
    tr[x] = tr[ls] + tr[rs]; return;
}
int Query(int L, int R, int l, int r, int x) {
    if(L <= l && r <= R) return tr[x]; pushdown(x, l, r); 
    int qwq = 0; if(L <= mid) qwq += Query(L, R, l, mid, ls);
    if(R > mid) qwq += Query(L, R, mid + 1, r, rs);
    return qwq;
}
bool check(int x) {
    Rep(i, 1, n) vis[i] = a[i] >= x;
    // for(int i=1;i<=n;i++) printf("%d ",vis[i]);printf("\n");
    mem(fla, -1) mem(tr, 0) Build(1, 1, n);
    Rep(i, 1, m) { int op = opt[i], l = ll[i], r = rr[i], now = Query(l, r, 1, n, 1);
        if(op == 0) Modify(r - now + 1, r, 1, n, 1, 1), Modify(l, r - now, 1, n, 1, 0);
        else Modify(l, l + now - 1, 1, n, 1, 1), Modify(l + now, r, 1, n, 1, 0);
    } return Query(Q, Q, 1, n, 1);
}
signed main() {
    // freopen("sort.in","r",stdin);
    // freopen("sort.out","w",stdout);
    n = read(), m = read();
    Rep(i, 1, n) a[i] = read();
    Rep(i, 1, m) opt[i] = read(), ll[i] = read(), rr[i] = read();
    Q = read(), x = 1, y = n;
    while(x <= y) { int Mid = x + y >> 1;// debug()
        if(check(Mid)) x = Mid + 1, ans = Mid;
        else y = Mid - 1;
    } printf("%d", ans);
    return 0;
}

这题好像还要用线段树分裂,这个到时候再学。

P3521 [POI2011]ROT-Tree Rotations

题目大意

给定一颗 \(n\) 个叶节点的树,每个叶节点(除了根)都有它的权值。并且保证 \(n\) 个权值可以构成一段 \(1 \to n\) 的排列。

对于这颗二叉树,满足每个节点不是叶节点就有左右 \(2\) 个子节点。你可以交换 \(1\) 个节点的两颗子树,求排列的先序遍历的最小逆序对数。

题解

首先考虑一对逆序对,可能有三种情况。

第一,在左子树中。

第二,在右子树中。

第三,跨越了左右子树。

那么,如果交换左右子树肯定不会影响前两种情况,那么只有尽可能的减小第三种情况。

我们用左儿子大小乘以右儿子大小即可得出逆序对个数。

还有一点,不管你怎么变,都无法改变比当前节点更浅的逆序对。

那么对于每颗子树维护线段树即可,向上传递时,做线段树合并操作即可。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#define maxn 2000010
#define maxm 1000010
#define ls x << 1
#define rs x << 1 | 1
#define inf 10000000000
#define inc(i) (++ (i))
#define dec(i) (-- (i))
#define mid ((l + r) >> 1)
#define int long long
#define XRZ 212370440130137957
#define debug() puts("XRZ TXDY");
#define mem(i, x) memset(i, x, sizeof(i));
#define Next(i, u) for(register int i = head[u]; i ; i = e[i].nxt)
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout);
#define Rep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i <= i##Limit ; inc(i))
#define Dep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i >= i##Limit ; dec(i))
int dx[10] = {1, -1, 0, 0};
int dy[10] = {0, 0, 1, -1};
using namespace std;
inline int read() {
    register int x = 0, f = 1; register char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
    return x * f;
} int num, sum, tot, n, ans;
struct node { int s, ss, num;} e[maxn << 4];
int update(int l, int r, int k) { int x = inc(tot);
	e[x].num = 1;// printf("%d ", x);
	if(l == r) return x;
	if(k <= mid) e[x].s = update(l, mid, k);
	else e[x].ss = update(mid + 1, r, k); return x;
}
int merge(int x, int y, int l, int r) {
	if(x == 0 || y == 0) return x + y;
	if(l == r) { e[x].num += e[y].num; return x;}
	num += e[e[x].ss].num * e[e[y].s].num;// printf("num : %d %d %d\n", num, e[x].ss, e[y].s);
	sum += e[e[y].ss].num * e[e[x].s].num;// printf("sum : %d %d %d\n", sum, e[x].s, e[y].ss);
	e[x].s = merge(e[x].s, e[y].s, l, mid);
	e[x].ss = merge(e[x].ss, e[y].ss, mid + 1, r);
	e[x].num += e[y].num; return x;
}
int dfs() { int x = read(), qwq; //e[0].num = 1;
	if(x == 0) { int L = dfs(), R = dfs(); num = 0, sum = 0;
		qwq = merge(L, R, 1, n); ans += min(num, sum);// printf("%d %d %d\n", num, sum, ans);
	} else qwq = update(1, n, x); return qwq;
}
signed main() { n = read(); dfs(); printf("%lld", ans);
	return 0;
}

P3224 [HNOI2012]永无乡

题目大意

你有 \(n\) 个点,每个点都有一个重要度。\(n\) 个点重要度构成一个 \(1 \to n\) 的排列。

你有 \(2\) 个操作。

其一,给你 \(2\) 个整数 \(x\) \(y\) ,表示在 \(x\)\(y\) 之间连一条边。

其二,给你 \(2\) 个整数 \(x\) \(k\) ,你需要输出所有与 \(x\) 相连的点中第 \(k\) 重要的点。

题解

代码比较好些,比模板好写多了。

这里把数据结构和连通性问题一起进行考察。

考虑对于每个连通块按照重要度建一颗权值线段树。

并把连通块内的所有点都放到一个并查集中。

对于操作一,我们只需要先把 \(2\) 和并查集合并,再把2个连通块的权值线段树进行合并。

对于操作二,找到 \(x\) 所在的权值线段树,查找第 \(k\) 为即可。

代码

\(scanf\) 读入会挂 \(90ps\)

关于字符的读入最好是用 \(cin\) 一般不会有人变态到取卡字符读入。

因为在 \(win\) 环境下 和 \(Linex\) 环境下 \(scanf\) 是不一样的。

在后者情况,会读入换行符。明明在 \(win\) 可以过。。

调了好久。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#define maxn 100010
#define maxm 3200010
#define ls x << 1
#define rs x << 1 | 1
#define inf 100000
#define inc(i) (++ (i))
#define dec(i) (-- (i))
#define mid ((l + r) >> 1)
//#define int long long
#define XRZ 212370440130137957
#define debug() puts("XRZ TXDY");
#define mem(i, x) memset(i, x, sizeof(i));
#define Next(i, u) for(register int i = head[u]; i ; i = e[i].nxt)
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout);
#define Rep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i <= i##Limit ; inc(i))
#define Dep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i >= i##Limit ; dec(i))
int dx[10] = {1, -1, 0, 0};
int dy[10] = {0, 0, 1, -1};
using namespace std;
inline int read() {
    register int x = 0, f = 1; register char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
    return x * f;
} int T[maxn], tot, fa[maxn]; char s;
struct node { int sum, l, r, fla;} e[maxm];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]);}
void pushup(int x) { e[x].sum = e[e[x].l].sum + e[e[x].r].sum;}
int Add(int x, int l, int r, int pos, int F) {
	if(x == 0) x = inc(tot);
	if(l == r) { inc(e[x].sum); e[x].fla = F; return x;}
	if(pos <= mid) e[x].l = Add(e[x].l, l, mid, pos, F);
	else e[x].r = Add(e[x].r, mid + 1, r, pos, F); pushup(x); return x;
}
int merge(int x, int y, int l, int r) {
	if(! x || ! y) return x + y;
	if(l == r) {
		if(e[y].fla) e[x].fla = e[y].fla, e[x].sum += e[y].sum;
		return x;
	}
	e[x].l = merge(e[x].l, e[y].l, l, mid);
	e[x].r = merge(e[x].r, e[y].r, mid + 1, r);
	pushup(x); return x;
}
int Query(int x, int l, int r, int k) {
	if(x == 0) return 0; int tmp = 0;
	if(e[x].sum < k) return 0;
	if(l == r) return e[x].fla;
	if(k <= e[e[x].l].sum) tmp = Query(e[x].l, l, mid, k);
	else tmp = Query(e[x].r, mid + 1, r, k - e[e[x].l].sum); return tmp;
}
signed main() { int n = read(), m = read();
	Rep(i, 1, n) { fa[i] = i; int x = read(); T[i] = Add(T[i], 1, n, x, i);}
	Rep(i, 1, m) { int x = read(), y = read(); x = find(x), y = find(y);
		fa[y] = x; T[x] = merge(T[x], T[y], 1, n);
	} int Q = read();
	while(Q --) { cin >> s;// printf("%c\n", s);
		if(s == 'B') { int x = read(), y = read(); x = find(x), y = find(y);
			if(x == y) continue; fa[y] = x; merge(T[x], T[y], 1, n);
		} else { int x = read(), k = read(); x = find(x);
			int now = Query(T[x], 1, n, k);
			if(now == 0) puts("-1");
			else printf("%lld\n", now);
			// now == 0 ? puts("-1") : printf("%d\n", now);
		}
	}
	return 0;
}

完结撒花!