题目描述
难度分:1900
输入n(2≤n≤2×105)和长为n−1的数组a(0≤a[i]≤2n)。
构造一个长为n的,0到n−1的排列b,满足a[i]=b[i]⊕b[i+1]。
输入保证有解。
输入样例1
4
2 1 2
输出样例1
0 2 3 1
输入样例2
6
1 6 1 4 1
输出样例2
2 3 5 4 0 1
算法
位运算
这个题主要就是分析性质,可以很容易发现b[i]=b[1]⊕⊕i−1j=1a[j]=b[1]⊕s[i−1],这样一来只要b[1]确定了,b数组的所有元素就确定了。由于题目保证有解,所以s数组的元素肯定是互不相同的,对于任意的b[1],也肯定满足b数组的任意两个元素都不同,那么就只需要保证最大值maxni=1b[i]<n即可。
于是我们可以把s中的所有元素都放入01
字典树,然后遍历x∈[0,n)。对于每个x,按位贪心看s中谁和x异或得到的值最大,这个最大值只要小于n,x就可以作为b[1]。
复杂度分析
时间复杂度
将a数组的前缀和插入到字典树中时间复杂度为O(nlog2A),获得b[1]的时间复杂度也是O(nlog2A)。最后根据b[1]构造出b数组的时间复杂度为O(n),所以整个算法的时间复杂度为O(nlog2A)。由于a[i]≤2n,其中A最大也是O(n)级别。
空间复杂度
空间消耗的瓶颈就是字典树,每一位都有0和1两个分支,一共有h层,空间复杂度大概是O(2h),本题中h<20。如果h较小,那么空间复杂度的瓶颈就在于a的前缀和数组s,它的空间消耗是铁的O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010;
int n, a, s[N];
class Node {
public:
int pass, end;
Node* nxt[2] = {NULL};
Node() {
pass = end = 0;
}
};
class Trie {
public:
const int MAX_HEIGHT = 20;
Node* root;
Trie() {
root = new Node;
}
void insert(int x) {
Node* cur = root;
for(int i = MAX_HEIGHT; ~i; i--) {
int bit = x>>i&1;
if(!cur->nxt[bit]) {
cur->nxt[bit] = new Node;
}
cur = cur->nxt[bit];
cur->pass++;
}
cur->end++;
}
int query(int x) {
Node* cur = root;
int ans = 0;
for(int i = MAX_HEIGHT; ~i; i--) {
int bit = x>>i&1;
if(cur->nxt[1 - bit]) {
ans |= 1<<i;
cur = cur->nxt[1 - bit];
}else {
cur = cur->nxt[bit];
}
}
return ans;
}
};
int main() {
scanf("%d", &n);
Trie* trie = new Trie();
for(int i = 1; i < n; i++) {
scanf("%d", &a);
s[i] = s[i - 1]^a;
trie->insert(s[i]);
}
int b1 = 0;
for(int x = 0; x < n; x++) {
if(trie->query(x) < n) {
b1 = x;
break;
}
}
printf("%d ", b1);
for(int i = 2; i <= n; i++) {
printf("%d ", b1^s[i - 1]);
}
return 0;
}