HDU 6955. Xor Sum題解


HDU 6955. Xor Sum

題目鏈接:HDU 6955. Xor Sum

題意:

給一個長度為\(n\)的一個整數序列\({a_n}\),尋找最短的,滿足異或和大於等於\(k\)的連續子序列。輸出子序列的左端點和右端點,若有多個最短長度的連續子序列,輸出位置靠前的。不存在滿足條件的連續子序列,輸出\(-1\)

輸入:

\(1\)行一個整數\(t(t\leq100)\),代表測試樣例個數。

對於每一個樣例:

\(1\)行,有兩個整數\(n(1\leq n\leq 10^5),k(0\leq k<2^{30})\),分別代表整數序列的長度和題意中的\(k\)

\(2\)行,有\(n\)個整數\(a_1,a_2,...,a_n(0\leq a_i<2^{30})\),代表這個整數序列。

輸出:

對於每一個樣例:

若存在這樣的連續子序列,輸出連續子序列的左端點和右端點。

若有多個最短長度的連續子序列,輸出位置靠前的那一個。

若不存在滿足條件的連續子序列,輸出\(-1\)

分析:

首先,異或滿足這樣的性質:

\[sum[l...r]=sum[1...l-1]\oplus{}sum[1...r] \]

這使得我們可以使用前綴異或和。

\(sum[1...i]=S_i\),特別地,定義\(S_0=0\),於是有

\[sum[l...r]=S_{l-1}\oplus S_{r} \]

從而可以將原問題轉化為,在\(S_0,S_1,...,S_n\)中尋找一對在序列中距離最短的\(S_i,S_j(i<j)\),滿足\(S_i\oplus S_j\geq k\),最終答案即為\([i+1,j]\)\(i+1\)是左端點,\(j\)是右端點)

最暴力的想法為:使用二層循環,第一層從\(1\)\(n\)枚舉距離\(d\),第二層枚舉\(i(0\leq i\leq n+1-d)\),判斷\(S_i\oplus S_{i+d-1}\geq k\)是否成立,成立則說明答案已經找到,跳出循環,不成立則繼續循環。

暴力做法時間復雜度為\(O(n^2)\),會超時,所以需要優化。不妨考慮枚舉右側端點\(i\),試試看能否在區間\([0,i-1]\)上快速找到離\(i\)最近的\(j\),使得\(S_j\oplus S_i\geq k\)

既然是異或問題,一定和二進制相關,而題目給出的范圍是\(0\leq a_i<2^{30}\),所以\(S_i\)也在這個范圍中,說明\(S_i\)可以用\(30\)位二進制表示,於是\(S_i\)可以看成長度為\(30\)的"01"字符串。

故而可以考慮在枚舉\(i\)的時候,動態地維護一個包含\(S_0,S_1,...,S_{i-1}\)的"01"字典樹,其中深度小的節點存儲高位,深度大的節點存儲低位。字典樹的每個節點附加存儲着這個節點所表示的前綴(從高位開始的"01"串)最后一次在數列\(S_0,S_1,...,S_{i-1}\)中出現的位置,沒出現過位置就記成\(-1\)

然后讓\(S_i\)和字典樹中的串進行帶剪枝的逐位異或:

為了方便描述,記\(S_i\)中從高位到低位數起第\(j\)位(以下簡稱“第\(j\)位”)為\(S_{ij}\)\(k\)中第\(j\)位是\(k_j\)

假設目前正在考慮第\(j\)位的情況,即到達了字典樹的第\(j-1\)層(根節點為空前綴,把它當成第\(0\)層),考慮往哪個方向上的子樹深入下去(並不是兩個方向上都需要深入,即剪枝)。

  1. \(k_j=1\),就要求字典樹中所表示串的\(j\)\(S_{ij}\)異或的結果也是\(1\),才有可能使得最終異或結果大於等於\(k\),由於\(S_{ij}\oplus1\)\(S_{ij}\)異或結果是\(1\),故此時需要往\(S_{ij}\oplus1\)方向的那個子樹上深入。

  2. \(k_j=0\),說明字典樹中所表示串的\(j\)\(S_{ij}\)異或的結果可以是\(0\),也可以是\(1\)

    • 若字典樹中所表示串的\(j\)\(S_{ij}\)異或的結果是\(1\),由於\(S_{ij}\oplus1\)\(S_{ij}\)異或結果是\(1\),即考慮往\(S_{ij}\oplus1\)方向,發現此時無需進行后續位的異或,也可知道最終異或結果大於\(k\),故無需往\(S_{ij}\oplus1\)方向的子樹下深入,直接利用往\(S_{ij}\oplus1\)方向的節點所附加的信息更新答案(別忘了,每個節點附加記錄了這個節點所代表前綴最后一次在數列\(S_0,S_1,...,S_{i-1}\)中出現的位置,這是一種剪枝,也是優化的關鍵)。

    • 若字典樹中所表示串的\(j\)\(S_{ij}\)異或的結果是\(0\),由於\(S_{ij}\)\(S_{ij}\)異或結果是\(0\),即考慮往\(S_{ij}\)方向,此時還需要進行后續位的異或,故需要往\(S_{ij}\)方向的子樹上深入。

假如逐位異或能夠進行到最后一位,那說明異或到最后才比較出大於等於\(k\),此時直接利用葉節點附加信息更新答案。

\(S_i\)與字典樹中的串進行帶剪枝的逐位異或之后,就需要把\(S_i\)這個串插入到字典樹中,注意插入過程需要更新節點的附加信息,以便后續計算。

時間復雜度分析:由於字典樹只會往一個方向遍歷,設整數序列最大的數為\(P\),則樹的最大深度是\(\log P\),整數序列長度為\(n\),故復雜度為\(O(n\log P)\),本題中可以認為是\(O(30n)\)

代碼:

#include <algorithm>
#include <cassert>
#include <cstdio>
using namespace std;
const int maxn = 1e5 + 10;
const int maxm = 3e6 + 10;
int a[maxn];
int ch[maxm][2], val[maxm];
void solve() {
    int n, k, tot = 1;
    scanf("%d%d", &n, &k);
    a[0] = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
        a[i] ^= a[i - 1];
    }
    ch[1][0] = ch[1][1] = 0;
    val[1] = -1;
    int ans_l = -1, ans_r = n + 1;
    for (int i = 0; i <= n; i++) {
        int now = 1, res = -1;
        for (int j = 29; j >= 0; j--) {
            int dig = (a[i] >> j) & 1;
            if ((k >> j) & 1) {          // k的當前位為1,只能和dig異或結果為1,才可能大於等於k
                now = ch[now][dig ^ 1];  // 與dig異或結果為1的數是dig^1
            } else {                     // k的當前位為0,和dig異或結果可以是1也可以是0
                if (ch[now][dig ^ 1]) {  // 和dig異或結果為1,后面的位都無須看,結果一定大於k
                    res = max(res, val[ch[now][dig ^ 1]]);
                }
                // 和dig異或結果是1的情況就無須遍歷,只需要遍歷和dig異或結果為0的情況
                now = ch[now][dig];
            }
            if (now == 0) {  // 節點沒了
                break;
            }
        }
        if (now) res = max(res, val[now]);
        if (res >= 0 && i - res < ans_r - ans_l) {
            ans_l = res;
            ans_r = i;
        }
        now = 1;
        for (int j = 29; j >= 0; j--) {
            int dig = (a[i] >> j) & 1;
            if (!ch[now][dig]) {
                ch[now][dig] = ++tot;
                ch[tot][0] = ch[tot][1] = 0;
                val[tot] = -1;
            }
            now = ch[now][dig];
            val[now] = max(val[now], i);
        }
    }
    if (ans_l == -1 && ans_r == n + 1) {
        puts("-1");
    } else {
        printf("%d %d\n", ans_l + 1, ans_r);
    }
}
int main() {
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM