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(在当前子树不可能找到),否则就递归遍历左右子树(如果存在的话)。
问题又来了:
这样对于每个右端点就可以找到一个与之对应的最优左端点,根据题意更新答案即可。
注意:数组不要开太大,以及初始化的时候不能无脑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; }