1. 程式人生 > 實用技巧 >不實習,如何增加Java專案經驗?!

不實習,如何增加Java專案經驗?!

線段樹合併

前置芝士 —— 動態開點

什麼是動態開點,是用於處理一些區間跨度比較大,空間比較小的題目。

比如:

\(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;
}

完結撒花!