來上數學了 :P
拓撲排序
我就用我之前做的簡報好了 :U
Kosaraju's Algorithm
SCC (Strongly Connected Components) 簡單來說
就是一群點,並且這群點中,任何的點都可以互相走到
通常用於有向圖中 因為一個連通塊不一定是 SCC
1. 對有向圖的每個點做DFS,直到每個點都被走過為止。
同時,我們紀錄每個點的exit順序 (就是他完整DFS後的順序)
2. 我們將這個順序倒過來,並且把每個邊都翻轉,
只要照這個順序看,這個點可以走到的點,都會在同一個SCC裡
假設我們有兩點A B,A的exit順序在B之後。
(這樣代表A可以走到B)
那我們將每個邊翻轉,並且從A看的時候 (exit順序比較晚的會先看)。
若A可以走到B,那就代表在原圖中B可以走到A,
就代表他們在同一個SCC內。
反之則否。
我們可以以此類推,假設有一群點他們exit順序都在點A前面,
我們就可以由上面的推論得出 這些點是否有和A在同一個SCC內了
void dfs(int x){
vis[x] = 1;
for (int i:e[x]){
if (vis[i]) continue;
dfs(i);
}
order.push_back(x);
}
void rdfs(int x){
vis[x] = 1;
for (int i:re[x]){
if (vis[i]) continue;
rdfs(i);
}
}for (int i=1; i<=m; ++i){
int u, v; cin >> u >> v;
e[u].push_back(v);
re[v].push_back(u);
}
for (int i=1; i<=n; ++i){
if (!vis[i]){
dfs(i);
}
}
reverse(order.begin(), order.end());
for (int i=1; i<=n; ++i) vis[i] = 0;
for (int i:order){
if (!vis[i]){
rdfs(i);
}
}模板題
應該可以看出來蠻 SCC 的
只要判斷整個圖是否為SCC就好了
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 100005
using namespace std;
int n, m;
vector<int> e[maxn], re[maxn];
vector<int> order, ans;
bool vis[maxn];
void dfs(int x){
vis[x] = 1;
for (int i:e[x]){
if (vis[i]) continue;
dfs(i);
}
order.push_back(x);
}
void rdfs(int x){
vis[x] = 1;
for (int i:re[x]){
if (vis[i]) continue;
rdfs(i);
}
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=1; i<=m; ++i){
int u, v; cin >> u >> v;
e[u].push_back(v);
re[v].push_back(u);
}
for (int i=1; i<=n; ++i){
if (!vis[i]){
dfs(i);
}
}
reverse(order.begin(), order.end());
for (int i=1; i<=n; ++i) vis[i] = 0;
for (int i:order){
if (!vis[i]){
rdfs(i);
ans.push_back(i);
}
}
if (ans.size() == 1) cout << "YES" << endl;
else{
cout << "NO" << endl;
cout << ans[1] << " " << ans[0] << endl; //in reverse, cus the graph is in reverse too
}
}先觀察到幾個性質:
1. 一個SCC中全部的金幣都可以被取到
2. 若將每個非在SCC內的邊留下
(就是當作把SCC連起來)
他一定會形成一個DAG (因為若有環,就又會形成SCC)
那我們就可以在這個新的圖上做dp找最大值就好了
(如果不會做可以考慮看看這題)
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 100005
using namespace std;
int n, m, a[maxn];
vector<int> e[maxn], re[maxn], se[maxn];
vector<int> order;
bool vis[maxn];
int cur, s[maxn], b[maxn];
int dp[maxn], ans;
void dfs(int x){
vis[x] = 1;
for (int i:e[x]){
if (vis[i]) continue;
dfs(i);
}
order.push_back(x);
}
void rdfs(int x){
vis[x] = 1;
s[cur] += a[x];
b[x] = cur;
for (int i:re[x]){
if (vis[i]) continue;
rdfs(i);
}
}
void fdfs(int x){
vis[x] = 1;
int cur = 0;
for (int i:se[x]){
if (!vis[i]) fdfs(i);
cur = max(cur, dp[i]);
}
dp[x] = s[x] + cur;
ans = max(ans, dp[x]);
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=1; i<=n; ++i) cin >> a[i];
for (int i=1; i<=m; ++i){
int u, v; cin >> u >> v;
e[u].push_back(v);
re[v].push_back(u);
}
for (int i=1; i<=n; ++i){
if (!vis[i]){
dfs(i);
}
}
reverse(order.begin(), order.end());
for (int i=1; i<=n; ++i) vis[i] = 0;
for (int i:order){
if (!vis[i]){
++cur;
rdfs(i);
}
}
for (int u=1; u<=n; ++u){
for (int v:e[u]){
se[b[u]].push_back(b[v]);
}
}
for (int i=1; i<=n; ++i) vis[i] = 0;
for (int i=1; i<=cur; ++i){
if (!vis[i]){
fdfs(i);
}
}
cout << ans << endl;
}簡單來說,就是每個括號內都只會有兩個變數
SAT問題通常給的形式會是 CNF,
簡單來說就是會是一群括號and起來
並且括號內的變數都取or
因為每個括號只有兩個變數,
我們先將題目假設為正確的,我們可以列出以下條件:
因為 或 我們一定要使一邊正確
1. 先觀察到以下特質:
2. 再觀察到這些特質:
假設我們有一個變數x,
1. 若 x 可以走到 not x,則x一定不成立 (互相矛盾)
2. 若 not x 可以走到 x,則 not x 一定不成立 (互相矛盾)
結論:
我們可以由此判斷是否有解,
因為若 x 和 not x 在同個 SCC 內,就不會有解
3. 再觀察到這些特質:
我們在Kosaraju's的時候,他有幫我們紀錄exit順序了,
因此我們可以用這個順序來判斷
1. x 與 not x 是否在同個 SCC 內
2. x 與 not x 哪個先哪個後 (依性質2推論出)
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 200005
using namespace std;
int n, m; vector<int> e[maxn], re[maxn];
vector<int> route;
bool vis[maxn], f=true;
int cur, s[maxn];
int ans[maxn];
void dfs(int x){
vis[x] = 1;
for (int i:e[x]){
if (vis[i]) continue;
dfs(i);
}
route.push_back(x);
}
void rdfs(int x){
vis[x] = 1;
s[x] = cur;
for (int i:re[x]){
if (vis[i]) continue;
rdfs(i);
}
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=1; i<=n; ++i){
char c, d; int u, v;
cin >> c >> u >> d >> v;
int a = 2*u, b = 2*v, nega = 2*u, negb = 2*v;
//true -> 2x, false -> 2x-1
if (c == '+') --nega;
else --a;
if (d == '+') --negb;
else --b;
e[a].push_back(negb);
e[b].push_back(nega);
re[negb].push_back(a);
re[nega].push_back(b);
}
for (int i=1; i<=2*m; ++i){
if (!vis[i]) dfs(i);
}
reverse(route.begin(), route.end());
for (int i=1; i<=2*m; ++i) vis[i] = 0;
for (int i:route){
if (!vis[i]){
++cur;
rdfs(i);
}
}
for (int i=1; i<=2*m; i+=2){
if (s[i] == s[i+1]) f = false;
else if (s[i] < s[i+1]) ans[i/2+1] = 0;
else ans[i/2+1] = 1;
}
if (!f) cout << "IMPOSSIBLE" << endl;
else{
for (int i=1; i<=m; ++i){
if (ans[i]) cout << '+';
else cout << '-';
if (i==m) cout << endl;
else cout << " ";
}
}
}
我們每個點都要有進和出,
那在這個route中的點進出的次數一定會一樣,所以可以推論出:
- 如果圖是無向邊:
除了出發和結束點,每個點的 degree 一定為偶數
- 如果圖是有向邊:
除了出發和結束點,每個點的 in degree = out degree
並且能進一步判斷
在無向圖中:
如果deg奇數的 = 0 -> 那就會是euler curcuit
如果deg奇數的 = 2 -> 那就會是euler tour
(那兩點就會是出發和結束點)
在有向圖中:
如果每個點 in degree = out degree -> 那就會是euler curcuit
如果有一個點 in = out+1,有另一個點 out = in+1 -> 那就會是euler tour
(那兩點就會是出發和結束點)
void dfs(int x){
while (!e[x].empty()){
int v = *e[x].begin();
e[x].erase(v);
e[v].erase(x);
dfs(v);
}
route.push_back(x);
}#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 200005
using namespace std;
int n, m; set<int> e[maxn];
int deg[maxn], f;
vector<int> route;
void dfs(int x){
while (!e[x].empty()){
int v = *e[x].begin();
e[x].erase(v);
e[v].erase(x);
dfs(v);
}
route.push_back(x);
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=1; i<=m; ++i){
int u, v; cin >> u >> v;
e[u].insert(v);
e[v].insert(u);
++deg[u];
++deg[v];
}
for (int i=1; i<=n; ++i){
if (deg[i]%2 == 1) ++f;
}
//f == 0 -> euler curcuit
//f == 2 -> euler tour (starts and end on both nodes)
if (f != 0) cout << "IMPOSSIBLE" << endl;
else{
dfs(1);
bool flag = false;
for (int i=1; i<=n; ++i){
if (!e[i].empty()){
flag = true;
break;
}
}
if (flag) cout << "IMPOSSIBLE" << endl;
else{
for (int i=0; i<route.size(); ++i){
if (i == route.size()-1) cout << route[i] << endl;
else cout << route[i] << " ";
}
}
}
}我們將每個可能的都標成一個點
我們點之間可以用一個有向邊表示他們相差多少
舉例來說:
01 -> 11 的邊會是 1,因為 01加上1 可以變出11
我們最後因為每個邊都要走過,
所以只要跑Euler Tour 就好了
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 16
using namespace std;
int n;
bool vis[1<<maxn];
set<int> e[1<<maxn];
vector<int> route;
void dfs(int x){
vis[x] = 1;
while (!e[x].empty()){
int i = *e[x].begin();
e[x].erase(i);
if (!vis[i]) dfs(i);
}
route.push_back(x&1);
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for (int i=0; i<=(1<<n)-1; ++i){
int v1=0, v2=0;
if (i & (1<<(n-1))){
v1 = (i<<1) - (1<<n);
v2 = ((i<<1) - (1<<n)) | 1;
}else{
v1 = (i<<1);
v2 = (i<<1) | 1;
}
e[i].insert(v1);
e[i].insert(v2);
}
dfs(0);
reverse(route.begin(), route.end());
for (int i=1; i<n; ++i) cout << 0;
for (int i:route) cout << i;
cout << endl;
}剩下就是你dp的能力了
(所以我沒有要講什麼 給你們寫寫看題目好了)
#include <bits/stdc++.h>
#define endl '\n'
#define maxn 200005
using namespace std;
int n, ans[maxn]; vector<int> v[maxn];
void dfs(int u, int cnt){
int curcnt = cnt;
for (int i: v[u]){
dfs(i, curcnt);
cnt += ans[i];
}
ans[u] = cnt;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for (int i=2; i<=n; ++i){
int a; cin >> a;
v[a].push_back(i);
}
dfs(1, 1);
for (int i=1; i<=n; ++i){
if (i==n) cout << ans[n]-1 << endl;
else cout << ans [i]-1 << " ";
}
}題目有可能有點難懂,我解釋一下:
我們目標是選出一些邊,
使每個點最多只會出現在這些邊一次
找出最多會有幾個邊
我們先發現一個事情:
我們對於每個點考慮他的subtree,有兩種情況:
1 - 取
我們取的話就會是找他leaf node中 不取最小的,
取與他的這條邊,且剩下的都加上他們各自最大值
2 - 不取
加上 leaf node 他們各自最大值
base case - 最底下的當然是 0 條邊
輸出點1的值就好了
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 200005
#define inf 1e18
#define pii pair<int, int>
using namespace std;
int n; vector<int> e[maxn];
pii dp[maxn]; //yes no take
void dfs(int x, int p){
if (e[x].size() == 1 && e[x][0] == p) return; //no more edges
int curmax=-inf, curidx=0;
for (int i:e[x]){
if (i==p) continue;
dfs(i, x);
if (dp[i].second - dp[i].first > curmax){
curmax = dp[i].second - dp[i].first;
curidx = i;
}
}
for (int i:e[x]){
if (i==p) continue;
dp[x].second += max(dp[i].first, dp[i].second);
if (i!=curidx) dp[x].first += max(dp[i].first, dp[i].second);
else dp[x].first += dp[i].second + 1;
}
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for (int i=1; i<=n-1; ++i){
int a, b; cin >> a >> b;
e[a].push_back(b);
e[b].push_back(a);
}
dfs(1, 0);
cout << max(dp[1].first, dp[1].second) << endl;
}首先先發現,樹root的答案非常好找 (就把每個點的depth加起來就好了),所以就把這個當base case
接下來,我們對一個點往leaf node轉移討論
假設目前點 u 要轉移到點 v,siz[x]代表 x 之 subtree的大小
dp[x]代表題目所求,可以整理出以下式子:
前段代表 v subtree被影響的,因為在 u 轉移到 v 的同時,v subtree點的距離都被 -1
後段代表 其他點,因為往下了一層,其他非 v subtree 的點距離都被 +1
DFS跑兩次,第一次找base case和紀錄 siz
第二次作轉移
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 200005
using namespace std;
int n, siz[maxn], dp[maxn]; vector<int> e[maxn];
void dfs1(int x, int p, int d){
siz[x] = 1;
dp[1] += d;
for (int v:e[x]){
if (v == p) continue;
dfs1(v, x, d+1);
siz[x] += siz[v];
}
}
void dfs2(int x, int p){
for (int v:e[x]){
if (v == p) continue;
dp[v] = (dp[x] - siz[v]) + (n - siz[v]);
dfs2(v, x);
}
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for (int i=1; i<=n-1; ++i){
int a, b; cin >> a >> b;
e[a].push_back(b);
e[b].push_back(a);
}
dfs1(1, 0, 0);
dfs2(1, 0);
for (int i=1; i<=n; ++i){
if (i==n) cout << dp[i] << endl;
else cout << dp[i] << " ";
}
}舉例來說,以下樹的直徑為5
找的方法其實非常簡單:
我們先找一個點,DFS找他最遠的點 u
在從點 u DFS找他最遠的點 v
u 和 v 的距離就會是樹直徑了
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 100005
using namespace std;
int n, maxd, snode;
vector<int> e[maxn];
void dfs(int x, int p, int d){
if (d > maxd){
maxd = d;
snode = x;
}
for(int i:e[x]){
if (i == p) continue;
dfs(i, x, d+1);
}
return;
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for (int i=1; i<=n; ++i){
int u, v; cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs(0, 0, 0);
dfs(snode, snode, 0);
cout << maxd << endl;
}
Proof:
假設我們取樹直徑的兩點爲 (s, t)
隨機取一點 u,要找他距離最遠的點 v
假設 v 不為 s 或 t,那就一定會有比 d(s, t) 更長的路徑
(在找樹直徑第一次dfs時就不會找到 s 或 t 了)
與原假設矛盾,得證
我們只要從 s 和 t 往外 dfs ,
紀錄他們到每個點的距離,最後再去取 max 就好了。
#include <bits/stdc++.h>
#define endl '\n'
#define maxn 200005
using namespace std;
int n, s, t, maxcnt; vector<int> v[maxn];
int dist1[maxn], dist2[maxn];
void dfs(int x, int p, int d, int r){
if (d > maxcnt){
maxcnt = d;
if (!r) s = x;
else t = x;
}
for (int i:v[x]){
if (i==p) continue;
dfs(i, x, d+1, r);
}
}
void find_dist(int x, int p, int d, int r){
if (!r) dist1[x] = d;
else dist2[x] = d;
for (int i:v[x]){
if (i==p) continue;
find_dist(i, x, d+1, r);
}
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for (int i=1; i<=n-1; ++i){
int a, b; cin >> a >> b;
v[a].push_back(b);
v[b].push_back(a);
}
dfs(1, 0, 0, 0);
maxcnt = 0;
dfs(s, 0, 0, 1);
find_dist(s, 0, 0, 0);
find_dist(t, 0, 0, 1);
for (int i=1; i<=n; ++i){
if (i==n) cout << max(dist1[i], dist2[i]) << endl;
else cout << max(dist1[i], dist2[i]) << " ";
}
}Euler Tour Technique
這也叫做 時間戳記
我們會發現,在一個點in和exit的時間區間內,他底下的都會是其subtree的點
(邏輯應該蠻簡單的 自己想想看)
void dfs(int x, int prev){
d[x] = ++timer;
for (int i:e[x]){
if (i==prev) continue;
dfs(i, x);
}
f[x] = ++timer;
}
實作扣也非常的簡單
注意:我們BIT的大小要開 2*n ,因為in和exit順序各占一個點
#include <bits/stdc++.h>
#define endl '\n'
#define maxn 200005
#define int long long
using namespace std;
int n, q, a[maxn], bit[2*maxn];
int timer, d[maxn], f[maxn];
int i1, i2, i3;
vector<int> e[maxn];
inline int lb(int x){
return x&(-x);
}
void modify(int x, int v){
while (x <= 2*n){
bit[x] += v;
x += lb(x);
}
}
int query(int x){
int cnt = 0;
while (x>0){
cnt += bit[x];
x -= lb(x);
}
return cnt;
}
void dfs(int x, int prev){
d[x] = ++timer;
for (int i:e[x]){
if (i==prev) continue;
dfs(i, x);
}
f[x] = ++timer;
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> q;
for (int i=1; i<=n; ++i) cin >> a[i];
for (int i=1; i<=n-1; ++i){
int u, v; cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1, 1);
for (int i=1; i<=n; ++i){
modify(d[i], a[i]);
}
for (int i=1; i<=q; ++i){
cin >> i1;
if (i1==1){
cin >> i2 >> i3;
modify(d[i2], i3-query(d[i2])+query(d[i2]-1));
}else{
cin >> i2;
cout << query(f[i2]) - query(d[i2]-1) << endl;
}
}
}
會發現每次query某點時,從那點的in或exit往上找,
就都會在父節點的in exit區間內。
代表我們每次modify時改整個區間,就可以改道它subtree每個點的值。
(舉例來說,改點1區間內全部的值,全部在點1 subtree 的點都會被改到)
因為某點的值 會影響到他subtree點的答案,
所以修改只要對他in和exit的區間做修改就好了
區間修改,單點查詢 -> BIT 套差分
#include <bits/stdc++.h>
#define endl '\n'
#define maxn 200005
#define int long long
using namespace std;
int n, q, a[maxn], bit[2*maxn]; vector<int> v[maxn];
int d[maxn], f[maxn], timer;
int i1, i2, i3;
inline int lb(int x){
return x&(-x);
}
int query(int x){
int cnt=0;
while (x>0){
cnt += bit[x];
x -= lb(x);
}
return cnt;
}
void modify(int x, int v){
while (x<=2*n){
bit[x] += v;
x += lb(x);
}
}
void dfs(int x, int p){
d[x] = ++timer;
for (int i:v[x]){
if (i==p) continue;
dfs(i, x);
}
f[x] = ++timer;
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> q;
for (int i=1; i<=n; ++i) cin >> a[i];
for (int i=1; i<=n-1; ++i){
cin >> i1 >> i2;
v[i1].push_back(i2);
v[i2].push_back(i1);
}
dfs(1, 0);
for (int i=1; i<=n; ++i){
modify(d[i], a[i]);
modify(f[i]+1, -a[i]);
}
for (int i=1; i<=q; ++i){
cin >> i1;
if (i1 == 1){
cin >> i2 >> i3;
modify(d[i2], i3-a[i2]);
modify(f[i2]+1, -(i3-a[i2]));
a[i2] = i3;
}else{
cin >> i2;
cout << query(d[i2]) << endl;
}
}
}
Binary Lifting
我們今天只要將往上的數字,轉換成二進位,
並每次都走 2^x 步,就可以達到 O(log n)了
void dfs(int x, int p){
up[x][0] = p; //x的前一個node
//類似dp的觀念,我們往上 2^n-1 兩次 就會是往上 2^n
for (int i=1; i<=l; ++i){
up[x][i] = up[up[x][i-1]][i-1]; //從x的前一個node慢慢轉移
}
for (int i:e[x]){
if (x == p) continue;
dfs(i, x);
}
}
我們開一個陣列 ,
up[i][j]代表第 i node它往上 2^j 個node
並且用dfs去預處理
這裡的
query 只要把k轉成二進位,然後往上找就好了
如果往上找的node=0,就沒有這個parent,就回傳 -1
#include <bits/stdc++.h>
#define endl '\n'
#define maxn 200005
#define lmaxn 20
using namespace std;
//ceil(log2(200005)) = 18
int n, q, l, up[maxn][lmaxn]; vector<int> e[maxn];
void dfs(int x, int p){
up[x][0] = p;
for (int i=1; i<=l; ++i){
up[x][i] = up[up[x][i-1]][i-1];
}
for (int i:e[x]){
if (x == p) continue;
dfs(i, x);
}
}
int query(int x, int k){
bool f = true;
for (int i=0; i<=l; ++i){
if (k==0) break;
if (k%2 == 1){
if (up[x][i] == 0){
f = false;
break;
}else{
x = up[x][i];
}
}
k /= 2;
}
if (!f) return -1;
else return x;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> q;
l = 18;
for (int i=2; i<=n; ++i){
int a; cin >> a;
e[a].push_back(i);
}
dfs(1, 0);
for (int i=1; i<=q; ++i){
int a, b; cin >> a >> b;
cout << query(a, b) << endl;
}
}
void dfs(int x, int p){
up[x][0] = p;
tin[x] = ++timer; //紀錄x in 的時間
for (int i=1; i<=l; ++i){
up[x][i] = up[up[x][i-1]][i-1];
}
for (int i:e[x]){
if (i == p) continue;
dfs(i, x);
}
tout[x] = ++timer; //紀錄x out 的時間
}
紀錄dfs的戳記,等一下會用到
bool is_parent(int u, int v){
//check if u is v's parent
return tin[u] <= tin[v] && tout[u] >= tout[v];
}
int lca(int u, int v){
if (is_parent(u, v)) return u;
if (is_parent(v, u)) return v;
for (int i=l; i>=0; --i){
//不斷的往上找,不要超過他們的LCA
if (!is_parent(up[u][i], v)) u = up[u][i];
}
//因為找到的是LCA的前一個,所以要再往上一層
return up[u][0];
}我們只要選一點,一直往上走,
直到他確定是另一點的祖先
用時間戳記來判斷
#include <bits/stdc++.h>
#define endl '\n'
#define maxn 200005
#define lmaxn 20
using namespace std;
//ceil(log2(200005)) = 18
int n, q, l, up[maxn][lmaxn]; vector<int> e[maxn];
int tin[maxn], tout[maxn], timer;
void dfs(int x, int p){
up[x][0] = p;
tin[x] = ++timer;
for (int i=1; i<=l; ++i){
up[x][i] = up[up[x][i-1]][i-1];
}
for (int i:e[x]){
if (i == p) continue;
dfs(i, x);
}
tout[x] = ++timer;
}
bool is_parent(int u, int v){
//check if u is v's parent
return tin[u] <= tin[v] && tout[u] >= tout[v];
}
int lca(int u, int v){
if (is_parent(u, v)) return u;
if (is_parent(v, u)) return v;
for (int i=l; i>=0; --i){
if (!is_parent(up[u][i], v)) u = up[u][i];
}
return up[u][0];
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> q;
l = 18;
for (int i=2; i<=n; ++i){
int a; cin >> a;
e[a].push_back(i);
}
dfs(1, 1);
for (int i=1; i<=q; ++i){
int v, u; cin >> v >> u;
cout << lca(v, u) << endl;
}
}
Heavy Light Decomposition
Centroid Decomposition
其實還有flow 但我不會