1. 程式人生 > >PAT 甲級 1151 LCA in a Binary Tree(30 分)

PAT 甲級 1151 LCA in a Binary Tree(30 分)

題目正文:

1151 LCA in a Binary Tree(30 分)

The lowest common ancestor (LCA) of two nodes U and V in a tree is the deepest node that has both U and V as descendants.

Given any two nodes in a binary tree, you are supposed to find their LCA.

Input Specification:

Each input file contains one test case. For each case, the first line gives two positive integers: M (≤ 1,000), the number of pairs of nodes to be tested; and N (≤ 10,000), the number of keys in the binary tree, respectively. In each of the following two lines, N distinct integers are given as the inorder and preorder traversal sequences of the binary tree, respectively. It is guaranteed that the binary tree can be uniquely determined by the input sequences. Then M lines follow, each contains a pair of integer keys U and V. All the keys are in the range of int

.

Output Specification:

For each given pair of U and V, print in a line LCA of U and V is A. if the LCA is found and A is the key. But if A is one of U and V, print X is an ancestor of Y. where X is A and Y is the other node. If U or V is not found in the binary tree, print in a line ERROR: U is not found.

 or ERROR: V is not found. or ERROR: U and V are not found..

Sample Input:

6 8
7 2 3 4 6 5 1 8
5 3 7 2 6 4 8 1
2 6
8 1
7 9
12 -3
0 8
99 99

Sample Output:

LCA of 2 and 6 is 3.
8 is an ancestor of 1.
ERROR: 9 is not found.
ERROR: 12 and -3 are not found.
ERROR: 0 is not found.
ERROR: 99 and 99 are not found.

 題目說明:

為了查詢兩個節點的最低公共祖先,即是查詢同時包含這兩個節點的最小子樹的根節點。這個根節點滿足在中序序列中出現在需要查詢LCA的兩個節點的中間。例如,要查詢中序中的第i個和第j個的LCA,顯然,為了使答案存在i≠j,不妨設i<j,那麼要查詢的中序中第a個出現的元素,其中i、j、a滿足i≤a≤j,然而,這個不等式只是滿足LCA的必要條件,非充分,而這個不等式的成立是由中序遍歷的特點決定的,這一點不懂的請自行去畫圖看看。

為了滿足充分性,我們還應該在滿足上述條件的前提下,在那些滿足處於第i個和第j個之間(包含)的元素中尋找在先序遍歷中出現的第一個元素。換句話說在先序遍歷中尋找第一個出現的滿足i≤a≤j的中序遍歷的第a個元素。為了方便理解,請看如下的一種情況

圖中紅色的節點均會在中序遍歷中出現在i及j的順序之間,但是隻有第a個才會在先序中第一個出現,這個是由最底層公共祖先這個設定決定的。

接下來,就是將中序順序的值和中序的順序對應起來,因為樹中的值是各不相同的,所以這個對映也是唯一的,別忘了記錄反函式。那麼在讀取先序序列的時候就可將先序序列按其值的中序順序的排列順序值來對映一遍。姑且稱其為被編碼的先序序列。

囉嗦一點,目的是在被編碼的先序序列中查詢到第一個出現的滿足i≤a≤j的a值,其中,i和j分別代表待查最底層公共祖先元素對對應的中序順序值,而a則是滿足出現在順序值i和j之間的順序值,當a滿足在被編碼的先序序列中從頭遍歷第一個出現的時候,這個a順序對應的就是最底層公共祖先。

想象一種更容易理解的過程,在使用中序序列將值編碼為順序出現的次序時,這個過程其實就是在搭建一個二叉搜尋樹。接下來在被編碼的先序序列中查詢滿足條件的值的過程,可以對應上二叉搜尋樹上的尋找到兩個節點的分支點(即最小公共子樹的根)的過程。

如何加速這個查詢過程呢?

無論樹狀陣列、線段樹還是分塊法僅適合儲存區間極大值、極小值或區間和等單值資訊,無法保證區間內一定存在處於極大值和極小值之間的某元素。但是,如果該區間記憶體在一個滿足條件的值,那麼區間內的極大值不可能小於min(i,j),同理區間內的極小值不能大於max(i,j),這是該區間記憶體在一個滿足條件(i≤a≤j)的值的必要條件,雖非充分,但是確實在一定程度上加速了尋找的過程,我們能夠通過兩次比較捨去一段肯定不合適的區間。但是,若(i≤區間極小值≤j)或(i≤區間極大值≤j),那麼區間內一定存在至少一個滿足條件的值,即為極小值或極大值。而(區間極小值≤i && j≤區間極大值)的情況下必須遍歷整個區間才能確定是否存在滿足條件的值。在這裡,分塊法的加速效果和實際的資料有序程度密切相關。

綜上所述,我擬採用如下思路求解:

1、使用雜湊桶實現接近常數的快速編解碼,通過呼叫unordered_map實現。

2、使用中序順序值的先序來尋找LCA,即根據節點值的唯一性搭建二叉搜尋樹。

3、使用分塊法在一定程度上加速尋找滿足範圍的第一個值。

4、使用輸入掛加速讀取操作。

實現程式碼:

#include<bits/stdc++.h>
using namespace std;
struct node{                //block maximum & minimum
    int Min=INT_MAX,Max=INT_MIN;
};
int read(){                 //fast read-in
    int input=0, flag=1;
    char c=getchar();
    while((c<'0' || c>'9') && c!='-')
        c=getchar();
    if(c=='-'){             //if below zero
        flag=0;
        c=getchar();
    }
    while(c>='0' && c<='9'){
        input=input*10 + c - '0';
        c=getchar();
    }
    return flag? input : ~input+1;
}
int main(){
    int n,m,tmp, blockid, x, y, a;
    cin >> m >> n;
    unordered_map<int,int> encrypt,decrypt;
    for(int i=1;i<=n;i++){
        tmp=read();
        encrypt[tmp]=i;         //encrypt procedure
        decrypt[i]=tmp;         //decrypt procedure
    }
    int blocksize = ceil( sqrt(n) );
    int blockcnt = n%blocksize? n/blocksize+1 : n/blocksize;
    vector<node> block(blockcnt);
    vector<int> pre(n);
    for(int i=0;i<n;i++){
        tmp=encrypt[read()];
        pre[i]=tmp;                                         //encrypt the pre-sequence
        blockid=i/blocksize;
        block[blockid].Max=max(block[blockid].Max,tmp);     //construction of block matrix
        block[blockid].Min=min(block[blockid].Min,tmp);
    }
    while(m--){
        x=read(), y=read();
        if(!encrypt[x] && !encrypt[y])                      //if both not in the tree
            printf("ERROR: %d and %d are not found.\n",x,y);
        else if(!encrypt[x] || !encrypt[y])                 //if one node is not in the tree
            printf("ERROR: %d is not found.\n",!encrypt[x]?x:y);
        else{
            x=encrypt[x], y=encrypt[y];                     //acquire the encrypted value of the two node
            int larger=max(x,y), smaller=min(x,y);
            bool found=false;
            for(blockid=0; !found ;blockid++){              //if not found, traverse all blocks
                if(block[blockid].Max<smaller || block[blockid].Min>larger)
                    continue;
                for(int i=blockid*blocksize;i<(blockid+1)*blocksize;++i){   //for a potential block, traverse all elements within the block
                    if(smaller<=pre[i] && pre[i]<=larger){
                        a=pre[i];
                        found=true;
                        break;
                    }
                }
            }
            if(a==x || a==y)
                printf("%d is an ancestor of %d.\n",a==x?decrypt[x]:decrypt[y],a==x?decrypt[y]:decrypt[x]); //decrypt & output result
            else
                printf("LCA of %d and %d is %d.\n",decrypt[x],decrypt[y],decrypt[a]);
        }
    }
    return 0;
}

執行時間截圖:

 

實現了較高的效率。