2021“MINIEYE杯”中国大学生算法设计超级联赛(1)1006.Xor sum
Xor sum
题目链接
http://acm.hdu.edu.cn/showproblem.php?pid=6955
题意
给定 \(n\) 个数,求异或和大于等于 \(k\) 的最短的区间左右端点,如果有多个答案,输出左端点编号最小的那个。
思路
由于异或的自反性,我们做个前缀异或和可将区间异或和转换成俩个数的异或和。
对于每一个 \(a_i\) ,要找到以 \(a_i\) 作为结尾的子串,只要和 \(i\) 之前的所有的异或前缀和做一次异或运算,就能得到以 \(i\) 为结尾的异或前缀和。
我们把前 \(i - 1\) 项结尾的异或前缀和用字典树进行拆位储存, 从高位往低位枚举, 类似数位dp, 将大于的答案纳入考虑范围,不断缩小左端点, 得到以 \(a_i\) 结尾的最短的目标区间。
AC_Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e7 + 50;
ll a[maxn];
int tot = 0;
int tree[maxn][2];
int cnt[maxn];
void insert(int x, int pos){
int rt = 0;
for(int i = 31;i >= 0;i--){
int now = x >> i & 1;
if(!tree[rt][now]){
tree[rt][now] = ++tot;
}
rt = tree[rt][now];
cnt[rt] = max(cnt[rt], pos);
}
}
void find(int x, int k, int &L){
int rt = 0;
for(int i = 31;i >= 0;i--){
int nowx = x >> i & 1;
int nowk = k >> i & 1;
if(!nowx){
if(!nowk){
L = max(L, cnt[tree[rt][1]]);
if(!tree[rt][0]) return;
rt = tree[rt][0];
}
else{
if(!tree[rt][1]) return;
rt = tree[rt][1];
}
}
else{
if(!nowk){
L = max(L, cnt[tree[rt][0]]);
if(!tree[rt][1]) return;
rt = tree[rt][1];
}
else{
if(!tree[rt][0]) return;
rt = tree[rt][0];
}
}
}
L = max(L, cnt[rt]);
}
int main()
{
std::ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--){
int n, k;
cin >> n >> k;
for(int i = 1;i <= n;i++){
cin >> a[i];
a[i] ^= a[i - 1];
}
ll len = n + 1;
ll ansL = 0;
for(int i = 1;i <= n;i++){
if(a[i] >= k){
ansL = i;
len = i;
break;
}
}
insert(0, 0);
for(int i = 1; i <= n;i++){
int L = 0;
find(a[i], k, L);
if(i - L < len && L){
len = i - L;
ansL = L + 1;
}
insert(a[i], i);
}
if(len != n + 1) cout << ansL << " " << ansL + len - 1 << endl;
else cout << -1 << endl;
for(int i = 0;i <= tot;i++) tree[i][0] = tree[i][1] = 0, cnt[i] = 0;
tot = 0;
}
return 0;
}