Tarjan離線演算法求LCA小結
求LCA的兩種做法不多解釋,這篇文章有詳細解釋。
以前以為轉RMQ法求LCA可以取代tarjan,實則不然,Tarjan不僅效率更高,而且可以維護一些路徑上的統計量。於是又離線Tarjan法做了一些題目。
poj 3728 The merchant
題意:有n做城市,每座城市有不同物價,給出Q個詢問(a,b),問從a到b的路徑上最大盈利(即:先在最小值買入,再在最大值賣出,只買賣一次)
解法:由於訊問的是路徑上的性質,因此肯定要用到lca,從a到b的最大贏利方案分三種情況:a--lca之間買入a--lca之間賣出;a--lca之間買入lca--b之間賣出,lca--b之間買入,lca--b之間賣出,由於a到b與b到a情況不同,incidence對每個點記錄4個值:
poj3417 Networkimport java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.StreamTokenizer; public class Main{ class node { int be, ne, val; node(int b, int e, int v) { be = b; ne = e; val = v; } } node buf[] = new node[200010], query[] = new node[200010], res[] = new node[100010]; int Eb[] = new int[100010], lb, Eq[] = new int[100010], lq, Er[] = new int[100010], lres; void addres(int a, int b, int v) { res[lres] = new node(b, Er[a], v); Er[a] = lres++; } void addedge(int a, int b) { buf[lb] = new node(b, Eb[a], 0); Eb[a] = lb++; buf[lb] = new node(a, Eb[b], 0); Eb[b] = lb++; } void addquery(int a, int b, int v) { query[lq] = new node(b, Eq[a], v); Eq[a] = lq++; query[lq] = new node(a, Eq[b], v); Eq[b] = lq++; } int f[] = new int[50010], vis[] = new int[50010]; int max[] = new int[50010], min[] = new int[50010], inf = 1 << 28; int up[]=new int[50010],down[]=new int[50010]; int find(int x) { int temp = f[x]; if (x != temp) f[x] = find(f[x]); up[x]=Math.max(Math.max(up[temp],up[x]), max[temp]-min[x]); down[x]=Math.max(Math.max(down[x],down[temp]),max[x]-min[temp]); max[x] = Math.max(max[x], max[temp]); min[x] = Math.min(min[x], min[temp]); return f[x]; } void init() { lq = lb = lres = 0; for (int i = 1; i <= n; i++) { f[i] = i; Er[i] = Eb[i] = Eq[i] = -1; max[i] = -inf; min[i] = inf; } } void dfs(int a) { vis[a] = 1; // 處理所以與a有關且b已經訪問過的查詢 for (int i = Eq[a]; i != -1; i = query[i].ne) { int b = query[i].be; if (vis[b] == 1) { int temp = find(b); addres(temp, a, i); } } // 處理子樹 合併子樹 for (int i = Eb[a]; i != -1; i = buf[i].ne) { int b = buf[i].be; if (vis[b] == 0){ dfs(b); f[b] = a; max[b]=Math.max(price[b], price[a]); min[b]=Math.min(price[b], price[a]); up[b]=price[a]-price[b]; down[b]=price[b]-price[a]; } } // 處理所有lca是a的查詢 for (int i = Er[a]; i != -1; i = res[i].ne) { int x = res[i].be; int k= res[i].val; int y = query[k].be; find(x); find(y); k=query[k].val; if(s[k]==x){ ans[k]=Math.max(up[x], down[y]); ans[k]=Math.max(ans[k], max[y]-min[x]); } else { ans[k]=Math.max(up[y], down[x]); ans[k]=Math.max(ans[k],max[x]-min[y]); } } } int n, m, price[] = new int[50010]; int s[] = new int[50010],ans[] = new int[50010]; StreamTokenizer in = new StreamTokenizer(new BufferedReader( new InputStreamReader(System.in))); int nextInt() throws IOException { in.nextToken(); return (int) in.nval; } void run() throws IOException { n = nextInt(); init(); for (int i = 1; i <= n; i++) price[i] = nextInt(); for (int i = 1; i < n; i++) addedge(nextInt(), nextInt()); m = nextInt(); for (int i = 1; i <= m; i++) { s[i] = nextInt(); addquery(s[i], nextInt(), i); } dfs(1); for (int i = 1; i <= m; i++) if(ans[i]>0) System.out.println(ans[i]); else System.out.println(0); } public static void main(String[] args) throws IOException { new Main().run(); } }
題意:在一棵樹上新增m條邊,從樹上選一條邊刪除,從m條中選一條刪除,問使的圖不連通的方案數。
分析:向樹中任意兩點間新增一條邊都會形成環,刪去一個環上的任意兩條邊都會是樹不連通,因此若樹上的邊與多於一條的新邊構成環刪除後無法使圖不連通,因此變為統計每條邊被環覆蓋的次數,設為點v的父邊被環覆蓋的次數為cnt[];對於,每條新增的邊<a,b>都會是a到lca和b到lca之間的邊被環覆蓋一次,cnt[a]++,cnt[b]++,cnt[lca]-=2,然後樹形dp統計cnt[p]=Sigma{cnt[son]};
由於離線tarjan在dfs過程中完成,因此可將樹形dp的過程合併到求lca過程中,這是轉rmq方法做不到的。
此題還要注意自環情況,不能新增雙向查詢(會被重複-2),被坑的好苦把模板里加了句return以示警戒
public class Main{
int maxn=100010;
class node {
int be, ne, val;
node(int b, int e, int v) {
be = b;
ne = e;
val = v;
}
}
node buf[] = new node[maxn*2], query[] = new node[maxn*2];
int Eb[] = new int[maxn], lb, Eq[] = new int[maxn], lq;
void addedge(int a, int b, int v) {
buf[lb] = new node(b, Eb[a], v);
Eb[a] = lb++;
buf[lb] = new node(a, Eb[b], v);
Eb[b] = lb++;
}
void addquery(int a, int b, int v) {
query[lq] = new node(b, Eq[a], v);
Eq[a] = lq++;
if(a==b)
return;
query[lq] = new node(a, Eq[b], v);
Eq[b] = lq++;
}
int f[] = new int[maxn], vis[] = new int[maxn];
int find(int x) {
if (x != f[x])
f[x] = find(f[x]);
return f[x];
}
void dfs(int a) {
vis[a] = 1;
// 處理子樹
for (int i = Eq[a]; i != -1; i = query[i].ne) {
int b = query[i].be;
if (vis[b] == 1) {
int temp = find(b);
cnt[temp] -= 2;
}
}
for (int i = Eb[a]; i != -1; i = buf[i].ne) {
int b = buf[i].be;
if (vis[b] == 0) {
dfs(b);
f[b]=a;
cnt[a]+=cnt[b];
}
}
}
int n, m, cnt[] = new int[100005];//
StreamTokenizer in = new StreamTokenizer(new BufferedReader(
new InputStreamReader(System.in)));
int nextInt() throws IOException {
in.nextToken();
return (int) in.nval;
}
void init() {
lq = lb = 0;
for (int i = 1; i <= n; i++) {
f[i] = i;
Eb[i] = Eq[i] = -1;
cnt[i] = vis[i] = 0;
}
}
void run() throws IOException {
while (in.nextToken() != in.TT_EOF) {
n = (int)in.nval;
m = nextInt();
init();
int a, b;
for (int i = 1; i < n; i++)
addedge(nextInt(), nextInt(), 0);
for (int i = 1; i <= m; i++) {
a = nextInt();
b = nextInt();
cnt[a]++;
cnt[b]++;
addquery(a, b, i);
}
dfs(1);
long ans = 0;
for (int i = 2; i <= n; i++){
if (cnt[i] == 0)
ans += m;
if (cnt[i] == 1)
ans++;
}
System.out.println(ans);
}
}
public static void main(String[] args) throws IOException {
new Main().run();
}
}
ural 1699 Turning Turtles
題意:一個h*w的矩陣中有的點可用有的點不可用,可以向四個方向移動,保證可用的兩點之間有且只有一條路徑(樹),問從a到b要拐幾次彎。
解法:樹上路徑顯然要lca,橫向邊用顏色1表示,縱向邊用顏色0表示,設num[v]為點v到當前根要拐幾次彎 color[v]表示點v父邊的顏色 ,last[v]表示點v到當前根最後一條邊的顏色。於是可以在合併和查詢時維護num陣列,雖然有點trivial。。。