Problem Description
Given a sequence of integers of length n, find the shortest consecutive subsequence witch XOR sum not less than k.
If there are multiple consecutive subsequences of the same length, print the consecutive subsequence with the smallest left end point.
If there are no consecutive subsequence witch XOR sum not less than k, just print "-1".
Input
The first line contains a single integer t (t<=100) representing the number of test cases in the input. Then t test cases follow.
The first line of each test case contains two integers n (1<=n<=100000) and k (0<=k<2^30), representing the length of sequence.
The second line of each test contains n integers ai (0<=ai<2^30), representing the integers in sequence.
The number of test witch n>1000 does not exceed 5.
Output
For each test case, print two integers in one line, representing the left end point and right end point of the consecutive subsequence.
If there are no consecutive subsequence witch XOR sum not less than k, print "-1" in one line.
Sample Input
2
3 2
1 2 2
9 7
3 1 3 2 4 0 3 5 1
Sample Output
2 2
5 7
感覺是很好的一道題,賽時寫的暴力二分+可持久化01trie直接t飛2333。但是補題的時候不管是看std還是好多大佬的博客,要么是直接按異或和最大做的要么是像std那樣用了奇奇怪怪的貪心思想(?)總感覺都不一定能保證找到最優解...有明白的大佬還請幫本蒟蒻解答一下QAQ。唯一看到的一份代碼https://blog.csdn.net/qq_39523236/article/details/118940552?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_utm_term~default-0.pc_relevant_baidujshouduan&spm=1001.2101.3001.4242感覺是最正確的了,本題解也是借鑒了這篇博客的思路Orz
首先這個題提到了區間異或和毫無疑問想到用01trie來寫。因為題目要求的是在滿足異或和大於等於k的前提下找到長度最小的一段區間,因此不妨先求出來異或前綴和(這樣求區間異或就可以O(1)來算了),然后枚舉區間的右端點,目標是找到一個滿足條件的左端點並更新答案。因此可以維護一棵01trie,在枚舉右端點的時候,先在里面查詢異或和大於等於k的離右端點最近的左端點(作為query函數的返回值),然后把當前位置的異或前綴和插入trie。那么查詢函數怎么寫呢?
回顧題目,要找的是滿足區間異或和大於等於k的情況下找最靠近右端點的左端點。不妨設枚舉到的位置為x,那么此時字典樹中插入的是sum[1], sum[2]....sum[x - 1],和之前常見的query寫法不同的是,每次查詢實際上執行的是對trie的一次dfs過程,在搜索過程中,如果走到一個節點時發現接下來不論往哪棵子樹遍歷,最終得到的區間異或和總是大於等於k,那么就直接返回覆蓋這個節點的最靠右的異或前綴和的下標(這和std的思路是一樣的,實際上這也是遞歸的出口),如果最終得到的區間異或和總是小於k則直接返回-1(在當前子樹不可能找到),否則就遞歸遍歷左右子樹(如果存在的話)。
問題又來了:
- 怎么知道覆蓋這個節點的最靠右的異或前綴和的下標呢?答:在insert的時候維護一個數組mx即可。因為是從左往右遍歷,因此插入時對於01鏈上的節點直接更新就OK。
- 怎么知道最終搜到的區間異或和是否一定大於等於k或者小於k呢?答:在不斷遞歸搜索的過程中傳遞一個參數sum表示當前搜索路徑上兩異或前綴和異或得到的區間異或和的前面一部分的大小,然后將其后面若干位全變為0或變為1來比較判斷。描述比較費勁,不妨看一個例子:設右端點對應的異或前綴和為01101,沿trie樹從高位往低位搜索到的部分為00,此時高兩位異或得到01,還剩下三位沒有搜索到,若k取小於等於01000即8的數,那么最終無論如何都能滿足要求,反之若k取大於10000即16的數那么最終必然不可能滿足要求。
這樣對於每個右端點就可以找到一個與之對應的最優左端點,根據題意更新答案即可。
注意:數組不要開太大,以及初始化的時候不能無腦memset
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 1e5 + 10;
int tol; //節點個數
int val[32 * MAXN]; //點的值
int ch[32 * MAXN][2]; //邊的值
int mx[32 * MAXN];
int n, k, a[100005], sum[100005];
void init() {
tol = 1;
ch[0][0] = ch[0][1] = 0;
for(int i = 0; i <= 32 * n; i++) mx[i] = 0;
}
void insert(int x, int pos)
{ //往 01字典樹中插入 x
int u=0;
for(int i = 31; i >= 0; i--) {
int v=(x>>i)&1;
mx[u] = max(mx[u], pos);//維護mx數組,順序更新實際上不需要max函數
if(!ch[u][v])
{ //如果節點未被訪問過
ch[tol][0]=ch[tol][1]=0; //將當前節點的邊值初始化
val[tol]=0; //節點值為0,表示到此不是一個數
ch[u][v]=tol++; //邊指向的節點編號
}
u=ch[u][v]; //下一節點
}
val[u]=x; //節點值為 x,即到此是一個數
mx[u] = max(mx[u], pos);
}
int f(int p, int sum, int i, int k, int x) {
int ans = -1;
if(sum >= k) {
//cout << "ok" << endl;
return mx[p];//沒必要繼續往下走了
}
if(sum + ((1ll << (i + 1)) - 1) < k) {//這么走下去不論怎么選都不可行
return -1;
}
if(ch[p][0]) {
ans = max(ans, f(ch[p][0], sum + (x & (1 << i)), i - 1, k, x));
}
if(ch[p][1]) {
ans = max(ans, f(ch[p][1], sum + ((x & (1 << i)) ^ (1 << i)), i - 1, k, x));
}
return ans;
}
int query(int x, int k) {
return f(0, 0, 31, k, x);
}
signed main() {
ios::sync_with_stdio(false);
int T;
cin >> T;
while (T --) {
init();
cin >> n >> k;
insert(0, 0);
int ansl = 0, ansr = 0, minlen = 0x3f3f3f3f;
for(int i = 1; i <= n; i++) {
cin >> a[i];
sum[i] = sum[i - 1] ^ a[i];
}
for(int i = 1; i <= n; i++) {
if(a[i] >= k) {
ansl = ansr = i;
minlen = 1;
break;
}
int lpos = query(sum[i], k);
if(lpos != -1 && (sum[i] ^ sum[lpos]) >= k) {
int len = i - lpos;
if(len < minlen) {
minlen = len;
ansl = lpos + 1, ansr = i;
} else if(len == minlen) {
if(ansl > lpos + 1) {
ansl = lpos + 1;
ansr = i;
}
}
}
insert(sum[i], i);
}
if(minlen != 0x3f3f3f3f) {
cout << ansl << " " << ansr << endl;
} else {
cout << -1 << endl;
}
}
return 0;
}