华中农业大学第五届程序设计大赛网络同步赛 H MathematicalGame

/**
Problem H MathematicalGame
题意:给出一个序列,让你寻找一个区间[L, R]使得序列在区间
[L, R]的异或值最大如果有多个则输出字典序最小的

思路:设sum(x)为区间[1, x]的异或和,要使得区间[L, R]的异或值
最大,即求出sum(R)^sum(L - 1)最大
首先sum(x)为O(n)时间,并且用一个数组记录值为sum的最小下标,
即index[sum(x)] = x; 对每个sum(x)拆成20位的二进制数,建立一
棵树,建树方法如下:根节点为0,从高位往低位扫描,如果扫描为
的数位为0,则新建一个左孩子(否则新建一个右孩子),当然有左(右)
孩子的时候不建, 例如5(00000000000000000101),建树过程就为:
左左左左左左左左左左左左左左左左左左右左右,并在根节点设置
index[5] = x (sum(x)=5且x最小)

然后开始扫描sum(i), 需要找到一个sum(j)使得sum(j) ^ sum(i)最大
如果j < i则区间为[j + 1, i], 否则为[i + 1, j]
假设要搜索的sum(j)为ans, 对于sum(i)的搜索方法如下:
假设sum(i) = 01010000000000001010(二进制), 从根节点开始搜索
先看第一位,因为为0,就看他是否有右孩子,如果有,则进入右孩子,
ans=ans*2+1,理由是因为异或要两个数位不同才会为1,高位为1则越大
否则进入他的左孩子,ans = ans*2(一定有左孩子,因为建树过程中能
到达的非叶子节点一定有右孩子,叶节点代表存在的值,就是说所有的
叶节点深度为21)

对于第二位同样,一直搜索到叶节点,那就是ans了,他的在记录下
index[ans], 那就是j了, 注意i有可能大于也有可能小于j,所以要
讨论一下i,j的大小
**/

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<stack>
#include<vector>
typedef long long ll;
const int maxn = 1e6 + 304;
const int INF = 1e9;
using namespace std;

int T, kase = 1, n, now;
int ind[maxn], dig[50];
int newL, newans, newR;
int ansL, maxans, ansR;
int son[maxn][2], a[maxn];

void dfs(int g, int o, int i) {
    if(i == 20) { newans = now ^ g; newR = ind[g]; return ; }
    int t = !dig[i];
    if(son[o][t]) {
        dfs((g << 1) + t, son[o][t], i + 1);
    } else {
        dfs((g << 1) + dig[i], son[o][t ^ 1], i + 1);
    }
}

void div(int k) {
    for(int j = 19; j >= 0; j--) {
        dig[j] = k & 1;
        k >>= 1;
    }
}

int main() {
    scanf("%d", &T);
    while(T--) {
        memset(ind, 0, sizeof ind);
        memset(son, 0, sizeof son);
        int sum = 0; maxans = -1;
        ansL = -1; ansR = -1; newans = 0;
        ind[0] = 0;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            sum ^= a[i];
            if(sum && !ind[sum]) ind[sum] = i;
            int o = 0;
            div(sum);
            for(int j = 0; j < 20; j++) {
                if(dig[j] & 1) {
                    son[o][1] = o * 2 + 2;
                    o = son[o][1];
                } else {
                    son[o][0] = o * 2 + 1;
                    o = son[o][0];
                }
            }
        }
        memset(dig, 0, sizeof dig);
        int o = 0;
        for(int j = 0; j < 20; j++) {
            if(dig[j] & 1) {
                son[o][1] = o * 2 + 2;
                o = son[o][1];
            } else {
                son[o][0] = o * 2 + 1;
                o = son[o][0];
            }
        }
        now = 0;
        for(int i = 1; i <= n; i++) {
            now ^= a[i]; div(now);
            newL = i;
            dfs(0, 0, 0);
            if(newL > newR) swap(newL, newR);
            newL++;
            if(newans > maxans) {
                maxans = newans;
                ansL = newL; ansR = newR;
            } else if(newans == maxans) {
                if(newL < ansL || (ansL == newL && newR < ansR)) {
                    ansL = newL; ansR = newR;
                }
            }
        }
        printf("Case #%d:\n", kase++);
        printf("%d %d\n", ansL, ansR);
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值