【日文题解搬运】XOR Spread

问题概要

对长度为 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} {ai1=ai1XORaiai+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=biXORbi1

解说 2:操作和辅助数列的关系

i i i 进行操作,相当于替换 b i − 1 b_{i-1} bi1 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,,bN1 进行重新排列操作

假设替换后的序列 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=biXORbi1,使得 b 1 , … , b N − 1 b_1,\dots,b_{N-1} b1,,bN1 重新排列后,数列正前面的元素的 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值