2021“MINIEYE杯”中国大学生算法设计超级联赛(1)1006 / HDU6955. Xor sum(01Trie/好题)
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;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
2020-07-21 2020杭电多校第一场 1004 Distinct Sub-palindromes(思维/构造)
2020-07-21 关于Typora行内显示公式的问题