1. 程式人生 > 其它 >143. 最大異或對

143. 最大異或對

題目傳送門

一、分析思路

//最大異或對
//用暴力是超時的
for(int i=0;i<n;i++)
   for(int j=0;j<i;j++)
      res=max(res,a[i]^a[j]);
  cout<<res<<endl;

結果不出意外,\(TLE\),只能想辦法進行優化

最終結論如下:

1、將整數解析為二進位制數,即有符號整數,31位,就是0-30,認為這31位是字元,按\(Trie\)樹進行儲存。

2、每個數字的每一個二進位制位,需要從高位到低位,即for(int i = 30; i >= 0; i--),原因是想像一下你在構建一個\(Trie\)

樹,那麼根就是最高位,然後一路走到\(31\)位,就是最低位。

3、構建就是這樣的,總結一下:
(1)\(Trie\)裡可以用來儲存數字,數字需要通過二進位制進行儲存。
(2)增加一個數字進來,其實就是增加了一個層級為\(31\)級的樹節點。
(3)假設最高位,肯定只有兩個數,\(0\)\(1\),當然,要是最高位是\(1\),那這個數就很大了~,就是說,大多數情況下,多個數是有一部分相同的路徑的,比如最開始的\(00000\)之類。
(4)放入一個數字,那麼它肯定會在任意一級(共\(31\)級)存在一邊,另一邊可能存在,也可能不存在。存在是因為另一個數字在那邊。也可能沒有數字在另一邊。
(5)上面的\(4\)

就解答了為什麼後面取反後找不到,就一定要本方能找到的問題。

4、你想找與自己高位不同的數字,但這個數字不一定就存在於字典中,比如你喜歡漂亮的,學歷高,脾氣好的,結果現實中沒有,只能退而求其次

5、那就在\(Trie\)中去\(Query\)時,儘量找每一位與自己不同的路徑,如果有的話,就走,如果沒有的話,就只能退而求其次。
其實,這個查詢是此演算法的難點和重點,下面主要討論這個問題:
(1)如果存在異或邊,也就是本位在Tire樹中存在取反值(0就是存在1,1就是存在0),那麼這個數就可以表示為
res = (res<<1) + !u; 否則就是 res = (res<<1) + u;

6、直到走到節點盡頭,就得到了與之異或最大的數字,然後計算兩個數字的最大異或,與最大值打擂臺。

C++ 程式碼

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010, M = N * 31; //M代表節點的最終個數,因為一共N個數字,每個數字的轉為二進位制都是31位(首位一般用於符號,所以不是32),N*31就是M了

int n;
int a[N], son[M][2], idx;

void insert(int x) {
    int p = 0;
    for (int i = 30; i >= 0; i--) {
        int u = x >> i & 1;
        if (!son[p][u])son[p][u] = ++idx;
        p = son[p][u];
    }
}

//這個查詢的含義: 在Trie樹中查詢到與x異或最大的數
int query(int x) {
    int p = 0, res = 0;
    for (int i = 30; i >= 0; i--) {
        int u = x >> i & 1;     //取出x的每一位,看看是0還是1
        if (son[p][!u]) {       //如果存在可以異或的路可以走的話
            //res = (res<<1) + !u; 
            res = res * 2 + !u; //這裡和10進位制是一樣的,比如 123=12*10+3,這裡是一模一樣的,只不過是二進位制,需要乘以2,另外,當前位由於走的是!u,所以要加上!u
        } else {
            p = son[p][u];      //否則只能走與自己本位一樣的路線
            res = (res<<1) + u;   //這裡和10進位制是一樣的,比如 123=12*10+3,這裡是一模一樣的,只不過是二進位制,需要乘以2,另外,當前位由於走的是u,所以要加上u
        }
    }
    return res;
}

int main() {
    //構建Trie樹
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
        //insert(a[i]);
        //這個insert放在這裡,還是放在下在的查詢迴圈內,都是可以的
        // 原因是我們需要找到 a(i) 與 a(i-1),a(i-2)....a(0)當中的最大值,沒必要向上找,因為每一個都向下找了,就覆蓋掉所有可能性了。
    }

    int res = 0;
    for (int i = 0; i < n; i++) {
        insert(a[i]);
        int t = query(a[i]);
        //打擂臺,看看哪組異或值最大
        res = max(res, a[i] ^ t);
    }
    printf("%d\n", res);
    return 0;
}