1. 程式人生 > >#1067 : 最近公共祖先·二

#1067 : 最近公共祖先·二

題目連結   及  思路參考

輸入

每個測試點(輸入檔案)有且僅有一組測試資料。

每組測試資料的第1行為一個整數N,意義如前文所述。

每組測試資料的第2~N+1行,每行分別描述一對父子關係,其中第i+1行為兩個由大小寫字母組成的字串Father_i, Son_i,分別表示父親的名字和兒子的名字。

每組測試資料的第N+2行為一個整數M,表示小Hi總共詢問的次數。

每組測試資料的第N+3~N+M+2行,每行分別描述一個詢問,其中第N+i+2行為兩個由大小寫字母組成的字串Name1_i, Name2_i,分別表示小Hi詢問中的兩個名字。

對於100%的資料,滿足N<=10^5,M<=10^5, 且資料中所有涉及的人物中不存在兩個名字相同的人(即姓名唯一的確定了一個人),所有詢問中出現過的名字均在之前所描述的N對父子關係中出現過,第一個出現的名字所確定的人是其他所有人的公共祖先

輸出

對於每組測試資料,對於每個小Hi的詢問,按照在輸入中出現的順序,各輸出一行,表示查詢的結果:他們的所有共同祖先中輩分最低的一個人的名字。


思路:

在最近公共祖先一中,每一次詢問都查詢一次。 詢問量大時:求解最近公共祖先的離線演算法。一次性收集若干詢問之後才能通過這個演算法一同計算出答案
深度優先搜尋的過程中對結點染色,如果發現當前訪問的結點是涉及到某個詢問,那麼就看這個詢問中另一個結點的顏色,如果是白色,則留待之後處理,如果是灰色,那麼最近公共祖先必然就是這個灰色結點,如果是黑色,那麼最近公共祖先就是這個黑色結點向上的第一個灰色結點
通過graph進行dfs,edge存查詢的所有連線(雙向),Edge的id表示查詢順序(1,2...)v表示查詢的另一點

結合並查集:每個結點最開始都是一個獨立的集合,每當一個結點由灰轉黑的時候,就將它所在的集合合併到其父親結點所在的集合中去。這樣無論什麼時候,任意一個黑色結點所在集合的代表元素就是這個結點向上的第一個灰色結點 AC程式碼:
class Edge {
	int v,id;
	Edge(int v,int id){  
        this.v = v;  
        this.id = id;  
    }
}
public class Main {
	static int n;
	static int m;
	static int[] ans;
	static int[] father; //存father的id
	static boolean[] hasfa;
	static int[] color; // 表顏色,0白色,1灰色,2黑色
	static Map<Integer,List<Integer>> graph; // 存孩子
	static Map<Integer, List<Edge>> edge;
	
	static Map<String, Integer> idx; // string與id對應
	static int id = 0;
	static String[] str; // 輸出時返回name
	
	public static void main(String[] args) {
		input();
		solve();
	}
	/*
	 * 輸入
	 */
	public static void input() {
		Scanner in = new Scanner(System.in);
		n = Integer.parseInt(in.nextLine());
		idx = new HashMap<String, Integer>();
		hasfa = new boolean[n * 2]; // 注意hasfa的size有可能超過n + 1
		str = new String[n * 2];
		color = new int[n * 2];
		father = new int[n * 2];
		
		graph = new HashMap<Integer,List<Integer>>();
		edge = new HashMap<Integer, List<Edge>>();
		
		ans = new int[n];
		for (int i = 0; i < n; i++) {
			String[] s = in.nextLine().split(" ");
			int id1 = get_idx(s[0]);
			int id2 = get_idx(s[1]);
			hasfa[id2] = true;
			father[i] = i;
			if (!graph.containsKey(id1)) {
				List<Integer> l = new ArrayList<Integer>();
				graph.put(id1,l);
			}
			graph.get(id1).add(id2);
		}
		m = Integer.parseInt(in.nextLine());
		for (int i = 1; i <= m; i++) {
			String[] s = in.nextLine().split(" ");
			int idx1 = get_idx(s[0]);  
	        int idx2 = get_idx(s[1]);
	        if (!edge.containsKey(idx1)) {
	        	List<Edge> l = new ArrayList<Edge>();
	        	edge.put(idx1, l);
			}
	        edge.get(idx1).add(new Edge(idx2, i));
	        if (!edge.containsKey(idx2)) {
	        	List<Edge> l = new ArrayList<Edge>();
	        	edge.put(idx2, l);
			}
			edge.get(idx2).add(new Edge(idx1, i));
		}
		in.close();
	}
	public static void solve() {
		int root = -1;
		for (int i = 1; i <= n; i++) {
			if (!hasfa[i]) {
				root = i;
			}
		}
		tarjan(root); // 從根節點開始dfs
		// 輸出結果
		for (int i = 1; i <= m; i++) {
			System.out.println(str[ans[i]]);
		}
	}
	public static void tarjan (int u) {
		color[u] = 1;
		if (edge.containsKey(u)) {
			for(int i = 0; i < edge.get(u).size(); i++) { // 遍歷所有查詢,是否有當前id
				int ID = edge.get(u).get(i).id; // 指查詢順序,1開始
				if (ans[ID] != 0) { 
					continue;
				}
				int v = edge.get(u).get(i).v;
				// 0代表未訪問過
				if (color[v] == 0) {
					continue;
				} else if (color[v] == 1) { // 1代表正在訪問,所以u和v的公共祖先就是v
					ans[ID] = v;
				} else if (color[v] == 2) { // 2表示訪問完畢的節點,所以u和v的公共祖先就是v當前的祖先
					ans[ID] = Find(v);
				}
			}
		}
		if (!graph.containsKey(u)) {
			return;
		}
		for (int i = 0; i < graph.get(u).size(); i++) {
			int vv = graph.get(u).get(i);
			tarjan(vv);
			// 一個節點dfs完畢,標記vv為訪問完畢的點,並更新父節點
			color[vv] = 2;
			father[vv] = u; // 注意這裡更新father
		}
	}
	/*
	 * 尋找x的祖先
	 */
	public static int Find(int x) {
		return x != father[x] ? father[x] = Find(father[x]) : father[x]; 
	}
	// 獲得name對應的id
	public static int get_idx(String name){  
	    if(idx.containsKey(name))  
	        return idx.get(name);  
	    idx.put(name, ++id);
	    str[id] = name;
	    return id;
	}
}