【線段樹】【CF1083C】 Max Mex
Description
給定一棵有 \(n\) 個點的樹,每個節點有點權。所有的點權構成了一個 \(0~\sim~n - 1\) 的排列。有 \(q\) 次操作,每次操作 \(1\) 為交換兩個點的點權,操作 \(2\) 為查詢 \(Mex(l)\) 值最大的 \(Mex(l)\) 值,其中 \(l\) 是樹上的一條路徑。定義一條路徑 \(l\) 的 \(Mex\) 值 \(Mex(l)\) 為這條路徑上最小的沒有出現過的自然數
Input
第一行是一個整數 \(n\) 代表點數
下面一行是 \(n\) 個數字,代表每個點的點權,保證是一個 \(0~\sim n - 1\) 的排列
下面一行有 \(n - 1\)
下面一行是一個整數 \(q\),代表操作個數
下面 \(q\) 行,每行一組操作。
如果該行第一個數字為 \(1\),則後面有兩個數字 \(x,y\),代表交換 \(x,y\) 的點權
否則該行有且僅有一個數字 \(2\),代表一次查詢
Output
對每次查詢輸出一行一個整數作為答案
Hint
\(2~\leq~n~\leq~2~\times~10^5~,~1~\leq~q~\leq~2~\times~10^5\)
Solution
一道線段樹維護圖上資訊的好題。
記得以前在某學堂學習時,PKU的HJC老師說線段樹出現在高階演算法競賽中,除了扔上來一個序列維護一堆東西以外,如果可以用線段樹解決,那麼原因一定是這道題目的資訊具有 可合併性
我們考慮這道題目,每次詢問可以轉化成查詢一個最小的 \(k\) 使得 \([0,k)\) 都出現在同一條鏈上。這個 \(k\) 顯然是滿足二分性質的,於是我們只需要一個能夠維護這樹節點權值字首的連通性的資料結構了。考慮如果相鄰的兩段權值區間的點可以被合併為一條鏈,那麼這段權值區間並顯然是合法的。我們考慮對每段區間維護過區間內所有值的鏈的兩個端點。注意這條鏈必須包含區間中的所有權值但是不一定僅包含這些權值。例如對於區間 \([1,2]\),這條鏈包含 \(\{1,2,4\}\) 也是合法的。
考慮合併時大區間合法當且僅當從兩個鏈的四個端點中選兩個做新的端點,剩下兩個點都在鏈上即可。參考
記兩端點為 \(u,v\),需要判定的點為 \(x\),\(LCA(u,v)~=~y\),則有:
\(x\) 在鏈 \((u,v)\) 上 \(\Leftrightarrow\) \[((LCA(u,x) == x~\lor~LCA(v,x) == x)~\land LCA(x,y) == y)\]
於是列舉一下兩個端點,判定一下即可。
發現在合併時我們會大量用到求LCA的操作,倍增顯然不夠優秀,於是我們使用ST預處理從而做到 \(O(1)\) 查詢。這樣pushup的複雜度就被我們降到了 \(O(w)\),其中 \(w\) 為列舉端點的情況數,即 \(C_4^2~=~6\)。所以修改的複雜度為 \(O(w \log n)\)
考慮查詢時,直接線上段樹上二分,維護字首鏈的連通性即可做到 \(O(w \log n)\)。
於是總複雜度 \(O((n + q)~w \log n)\),其中 \(w~=~6\),可以通過本題
Code
#include <cmath>
#include <cstdio>
#include <vector>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define putchar(o)\
puts("I am a cheater!")
#define freopen(a, b, c)
#endif
#define rg register
#define ci const int
#define cl const long long
typedef long long int ll;
namespace IPT {
const int L = 1000000;
char buf[L], *front=buf, *end=buf;
char GetChar() {
if (front == end) {
end = buf + fread(front = buf, 1, L, stdin);
if (front == end) return -1;
}
return *(front++);
}
}
template <typename T>
inline void qr(T &x) {
rg char ch = IPT::GetChar(), lst = ' ';
while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
if (lst == '-') x = -x;
}
template <typename T>
inline void ReadDb(T &x) {
rg char ch = IPT::GetChar(), lst = ' ';
while ((ch > '9') || (ch < '0')) lst = ch, ch = IPT::GetChar();
while ((ch >= '0') && (ch <= '9')) x = x * 10 + (ch ^ 48), ch = IPT::GetChar();
if (ch == '.') {
ch = IPT::GetChar();
double base = 1;
while ((ch >= '0') && (ch <= '9')) x += (ch ^ 48) * ((base *= 0.1)), ch = IPT::GetChar();
}
if (lst == '-') x = -x;
}
namespace OPT {
char buf[120];
}
template <typename T>
inline void qw(T x, const char aft, const bool pt) {
if (x < 0) {x = -x, putchar('-');}
rg int top=0;
do {OPT::buf[++top] = char(x % 10 + '0');} while (x /= 10);
while (top) putchar(OPT::buf[top--]);
if (pt) putchar(aft);
}
const int maxn = 2000010;
const int maxm = 4000010;
int n, q, vistime, t;
int MU[maxn], fa[maxn], sz[maxn], dfn[maxm], pre[maxn], ST[30][maxn], deepth[maxn], rmp[maxn], dre[maxn];
std::vector<int> son[maxn];
struct Tree;
void dfs(ci);
void Make_ST();
int ask(Tree*);
void buildpool();
int cmp(ci&, ci&);
int Get_Lca(ci, ci);
void update(Tree*, ci);
bool Is_Lca(ci, ci, ci);
void build(Tree*, ci, ci);
void check(Tree*, Tree*, Tree*);
struct Tree {
Tree *ls, *rs;
int l, r;
int v[2];
inline void pushup() {
if (!this->rs) {this->v[0] = this->ls->v[0]; this->v[1] = this->ls->v[1];}
else if (!this->ls) {this->v[0] = this->rs->v[0]; this->v[1] = this->rs->v[1];}
else if (!(~this->ls->v[0])) {
this->v[0] = this->v[1] = -1;
} else if (!(this->rs->v[0])) {
this->v[0] = this->v[1] = -1;
} else {
check(this->ls, this->rs, this);
}
}
};
Tree *pool[maxm], qwq[maxm], *rot, pree;
int top;
int main() {
freopen("1.in", "r", stdin);
qr(n);
for (rg int i = 1; i <= n; ++i) {qr(MU[i]); ++MU[i];}
for (int i = 2; i <= n; ++i) {
qr(fa[i]); son[fa[i]].push_back(i); ++sz[fa[i]]; ++dre[fa[i]]; ++dre[i];
}
bool _flag = true;
for (rg int i = 1; i <= n; ++i) _flag &= dre[i] <= 2;
if (_flag) {
qr(q);
int a;
while (q--) {
a = 0; qr(a);
if (a == 2) {
qw(n, '\n', true);
} else {
qr(a); qr(a);
}
}
return 0;
}
dfs(1);
Make_ST();
for (rg int i = 1; i <= n; ++i) rmp[MU[i]] = i;
buildpool();
build(rot, 1, n);
qr(q); int a, b, c;
while (q--) {
a = 0; qr(a);
if (a == 2) {
pree.v[1] = pree.v[0] = 0; qw(ask(rot) - 1, '\n', true);
}
else {
b = c = 0; qr(b); qr(c);
std::swap(rmp[MU[b]], rmp[MU[c]]); std::swap(MU[b], MU[c]);
update(rot, MU[b]); update(rot, MU[c]);
}
}
return 0;
}
void dfs(ci u) {
dfn [pre[u] = ++vistime] = u;
for (int i = 0; i < sz[u]; ++i) {
deepth[son[u][i]] = deepth[u] + 1;
dfs(son[u][i]); dfn[++vistime] = u;
}
}
void Make_ST() {
int dn = n << 1;
t = log2(dn) + 1;
for (rg int i = 1; i < dn; ++i) ST[0][i] = dfn[i];
for (rg int i = 1; i <= t; ++i) {
rg int di = i - 1;
for (rg int l = 1; l < dn; ++l) {
int r= l + (1 << i) - 1;
if (r >= dn) break;
ST[i][l] = cmp(ST[di][l], ST[di][l + (1 << di)]);
}
}
}
int cmp(ci &_a, ci &_b) {
if (deepth[_a] < deepth[_b]) return _a;
return _b;
}
void buildpool() {
for (rg int i = 0; i < maxm; ++i) pool[i] = qwq + i;
rot = pool[maxm - 1]; top = maxm - 2;
}
void build(Tree *u, ci l, ci r) {
if ((u->l = l) == (u->r = r)) {u->v[1] = u->v[0] = rmp[l]; return;}
int mid = (l + r) >> 1;
if (l <= mid) build(u->ls = pool[top--], l, mid);
if (mid < r) build(u->rs = pool[top--], mid + 1, r);
u->pushup();
}
int Get_Lca(ci u, ci v) {
int l = pre[u], r = pre[v];
if (l > r) std::swap(l, r);
int len = r - l + 1, T = log2(len);
return cmp(ST[T][l], ST[T][r - (1 << T) + 1]);
}
bool Is_Lca(ci u, ci v, ci x) {
int _tmp = Get_Lca(u, v);
return (((Get_Lca(u, x) == x) || (Get_Lca(v, x) == x)) && (Get_Lca(_tmp, x) == _tmp));
}
#define doit \
if (Is_Lca(v1, u1, x) && (Is_Lca(v1, u1, y))) {\
u->v[0] = u1; u->v[1] = v1;\
return;\
}
void check(Tree *ls, Tree *rs, Tree *u) {
if (ls->v[0] == 0) { *u = *rs; return;}
int u1, v1, x, y;
{
u1 = ls->v[0]; v1 = ls->v[1]; x = rs->v[0]; y = rs->v[1];
doit;
}
{
u1 = rs->v[0]; v1 = ls->v[0]; x = rs->v[1]; y = ls->v[1];
doit;
}
{
u1 = rs->v[1]; v1 = ls->v[1]; x = rs->v[0]; y = ls->v[0];
doit;
}
{
u1 = rs->v[1]; v1 = ls->v[0]; x = rs->v[0]; y = ls->v[1];
doit;
}
{
u1 = rs->v[0]; v1 = ls->v[1]; x = rs->v[1]; y = ls->v[0];
doit;
}
{
u1 = rs->v[0]; v1 = rs->v[1]; x = ls->v[0]; y = ls->v[1];
doit;
}
u->v[0] = u->v[1] = -1;
}
#undef doit
void update(Tree *u, ci pos) {
if ((u->l > pos) || (u->r < pos)) return;
if (u->l == u->r) {u->v[0] = u->v[1] = rmp[pos]; return;}
if (u->ls) update(u->ls, pos);
if (u->rs) update(u->rs, pos);
u->pushup();
}
int ask(Tree *u) {
Tree _tmp;
if ((u->ls)) {
check(&pree, u->ls, &_tmp);
if (~_tmp.v[0]) {
pree = _tmp; return u->rs ? ask(u->rs) : u->r;
} else {
return ask(u->ls);
}
} else if (u->rs) return ask(u->rs);
else return u->r;
}
Summary
判斷一個點在一條鏈上的方法為:它和鏈的其中一個端點的LCA是他自己且它和這條鏈頂端的節點的LCA是頂端節點。
當LCA查詢極多且難以離線處理時,考慮使用ST預處理尤拉遍歷序做到 \(O(1)\) 查詢
注意ST儲存尤拉遍歷序需要開 \(2n\) 的空間