1. 程式人生 > >Tarjan離線演算法求LCA小結

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個值:

up[v] 表示從v到目前的根的最大盈利,down[v] 從目前的根到v的最大盈利,Max[v]表示到目前的根的最大值,Min[v]表示到目前的根的最小值,在並查集合並和路徑壓縮時根據意義更改值。

import 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();
	}
}
poj3417 Network

題意:在一棵樹上新增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。。。