问题概要
对长度为 N N N 的数列 a 1 , … , a N a1,\dots,a_N a1,…,aN 进行如下操作:
- 选择满足条件 1 < i < N 1<i<N 1<i<N 的 i i i
-
{
a
i
−
1
=
a
i
−
1
XOR
a
i
a
i
+
1
=
a
i
+
1
XOR
a
i
\begin{cases}a_{i-1}=a_{i-1}\text{XOR} a_i\\a_{i+1}=a_{i+1}\text{XOR}a_i\end{cases}
{ai−1=ai−1XORaiai+1=ai+1XORai更新值
返回可以得到的字典序最小的数列
解说 1:导入辅助数列
定义数列
b
0
,
…
,
b
N
b_0,\dots,b_N
b0,…,bN,其中
b
i
=
{
0
(
如果
i
=
0
)
XOR
k
=
1
i
(
a
k
)
(
如果
i
>
0
)
b_i=\begin{cases}0 &(\text{如果} i=0)\\\text{XOR}_{k=1}^{i}(a_k) &(如果i>0)\end{cases}
bi={0XORk=1i(ak)(如果i=0)(如果i>0)
- 例如输入 2 , a 1 , … , a 5 = 1 , 1 , 3 , 2 , 1 {a_1,\dots,a_5}={1, 1, 3, 2, 1} a1,…,a5=1,1,3,2,1,此时 b 0 , b 1 , … , b 5 = 0 , 1 , 0 , 3 , 1 , 0 {b_0, b_1, \dots, b_5}={0,1,0,3,1,0} b0,b1,…,b5=0,1,0,3,1,0
- 通过定义可以得到 a i = b i XOR b i − 1 a_i = b_i \text{XOR} b_{i-1} ai=biXORbi−1
解说 2:操作和辅助数列的关系
对 i i i 进行操作,相当于替换 b i − 1 b_{i-1} bi−1 和 b i b_i bi 的值
解说 3: 获得字典序最小附列的方法
对于 a 1 , … , a N a1,\dots,a_N a1,…,aN 进行操作,相当于对于序列 b 0 , … , b N b_0,\dots,b_N b0,…,bN 中, b 1 , … , b N − 1 b_1,\dots,b_{N-1} b1,…,bN−1 进行重新排列操作
假设替换后的序列 b 0 ′ , … , b N ′ b_0',\dots,b_N' b0′,…,bN′ 对应着字典序最小的数列 a 1 ′ , … , a N ′ a_1',\dots,a_N' a1′,…,aN′。我们要根据公式 a i ′ = b i ′ XOR b i − 1 ′ a_i'=b_i'\text{XOR}b_{i-1}' ai′=bi′XORbi−1′,使得 b 1 , … , b N − 1 b_1,\dots,b_{N-1} b1,…,bN−1 重新排列后,数列正前面的元素的 XOR \text{XOR} XOR 操作结果最小。
解说 4:重新排列操作的计算量
寻找并取出使得 XOR \text{XOR} XOR 操作最小的元素如果使用 Binary Trie,可以在 O ( l o g ( m a x ( a i ) ) ) O(log(max(a_i))) O(log(max(ai))) 的时间复杂度完成。
同样的操作重复 N N N 次,总共复杂度为 O ( N l o g ( m a x ( a i ) ) ) O(Nlog(max(a_i))) O(Nlog(max(ai)))
AC 代码
- 该代码并不是日文题解内容
-
- 说起来,不知道为啥,日文题解很少有提供代码的
#include <cstdio>
#include <cstdlib>
#include <cassert>
#ifdef DEBUG
constexpr int maxn = 100;
#else
constexpr int maxn = 1e5+10;
#endif
constexpr int max_depth = 30;
int a[maxn], b[maxn];
int left[maxn<<6], right[maxn<<6];
int val[maxn<<6];
int root = 1;
int cnt = 2;
int get_new_node(){
return ++cnt;
}
void add_number(int x, int depth=30, int cur=1){
#ifdef DEBUG
if (depth==30){
printf("add:%d\n", x);
}
#endif
if (depth==-1){
val[cur]++;
return;
}
val[cur]++;
if (x&(1<<depth)){ // add left
if (left[cur] == 0){
left[cur] = get_new_node();
}
add_number(x, depth-1, left[cur]);
} else {
if (right[cur] == 0){
right[cur] = get_new_node();
}
add_number(x, depth-1, right[cur]);
}
}
int get_nearest_number(int x, int depth=30, int cur=1){
assert(val[cur] != 0);
if (depth==-1){
val[cur]--;
return 0;
}
val[cur]--;
int ans = 0;
if (x&(1<<depth)){ // left
if (val[left[cur]]){
ans = get_nearest_number(x, depth-1, left[cur]);
ans |= 1<<depth;
} else {
ans = get_nearest_number(x, depth-1, right[cur]);
}
} else {
if (val[right[cur]]){
ans = get_nearest_number(x, depth-1, right[cur]);
} else {
ans = get_nearest_number(x, depth-1, left[cur]);
ans |= 1<<depth;
}
}
return ans;
}
int get_min(int depth=30, int cur=1){
if (depth == -1){
val[cur]--;
return 0;
}
val[cur]--;
if (val[right[cur]]) {
return get_min(depth-1, right[cur]);
} else {
return (1<<depth) + get_min(depth-1, left[cur]);
}
}
void print(int depth=30, int cur=1, int bit=0){
#ifdef DEBUG
if (depth==-2){
return;
}
for (int i=30; i>depth; i--){
printf(" ");
}
if (val[cur] == 0){
printf("[/]\n");
return;
}
printf("%d\t%d\n", bit, val[cur]);
print(depth-1, right[cur], 0);
print(depth-1, left[cur], 1);
#endif
}
int main(){
int n;
scanf("%d", &n);
for (int i=1; i<=n; i++){
scanf("%d", a+i);
}
if (n==1){
printf("%d\n", a[1]);
return 0;
} else if (n==2){
printf("%d %d\n", a[1], a[2]);
return 0;
}
for (int i=1; i<n; i++){
b[i] = b[i-1]^a[i];
add_number(b[i]);
}
b[n] = b[n-1]^a[n];
print();
int cur = get_min();
print();
printf("%d ", cur);
for (int i=2; i<n; i++){
int nxt = get_nearest_number(cur);
#ifdef DEBUG
printf("output:");
#endif
printf("%d ", cur^nxt);
cur = nxt;
#ifdef DEBUG
printf("\n");
print();
#endif
}
printf("%d\n", b[n]^cur);
return 0;
}