牛客-1114E 老瞎眼 pk 小鲜肉(思维 + 离线 + 线段树 - 维护区间最小值)

6 篇文章 0 订阅
2 篇文章 0 订阅

题目链接:https://ac.nowcoder.com/acm/contest/1114/E

题意:给你n个数和Q次查询,每次查询问你区间[L,R]内使得a[l] ^ … ^ a[r] == 0的最小的区间长度。

思路:看了好多博客才看懂的一道题。首先我们先预处理出对于每个i,离它最近的一个j,并用pre数组记录,及pre[i] = j,使得a[j] ^ … ^ a[i] == 0。至于如何通过一次for循环将这个预处理出来呢?
我们考虑一下异或的性质,相同的书异或为0,那么我们就可以用一个pos数组将每一个前缀异或和出现的位置给记录下来,那么后面我们每一次求出一个异或和之后我们都查询一下pos[sum]是否为-1,如果不是,那么更新pre[i] = pre[sum] + 1;
这是因为
a[1] ^ … ^ a[pre[sum]] = sum;
a[1] ^ … ^ a[i] = sum;
将以上两式异或起来,就可以得到 a[pre[sum] + 1] ^ … ^ a[i] = 0;
代码如下:

memset(pos,-1,sizeof(pos)); //初始化
int sum = 0;
pos[0] = 0;
for(int i = 1 , x ; i <= n ; i++) {
    scanf("%d",&x);
    sum ^= x;
    if(pos[sum] != -1) pre[i] = pos[sum] + 1;
    else pre[i] = -1;
    pos[sum] = i;
}

之后便是离线查询啦。先将所有的查询用一个结构体存下来,之后按照右端点排序,然后从1开始遍历,每次如果pre[i] != -1,就在线段树中将pre[i] 的值修改为 i - pre[i] + 1。之后用一个while循环将每一个右端点为i的答案更新。代码如下:

for(int i = 1 ; i <= n ; i++) {
   if(pre[i] != -1) {
        update(pre[i] , i - pre[i] + 1 , 1 , n , 1);
    }
    while(a[cnt].r == i) {
        ans[a[cnt].id] = query(a[cnt].l,a[cnt].r,1,n,1);
        cnt++;
    }
}

总的代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

#define lson l , m ,rt << 1
#define rson m + 1 , r , rt << 1 | 1

const int maxn = 5e5 + 7;
const int INF = 0x3f3f3f3f;

struct node {
    int l,r;
    int id;
}a[maxn];

bool cmp(node aa,node bb) {
    return aa.r < bb.r;
}

int MIN[maxn << 2];

void push_up(int rt) {
    MIN[rt] = min(MIN[rt<<1],MIN[rt<<1|1]);
}

void build(int l,int r,int rt) {
    MIN[rt] = INF;
    if(l == r) return ;
    int m = (l + r) >> 1;
    build(lson) , build(rson);
}

void update(int x,int y,int l,int r,int rt) {
    if(l == r) {
        MIN[rt] = y;
        return ;
    }
    int m = (l + r) >> 1;
    if(x <= m) update(x,y,lson);
    else update(x,y,rson);
    push_up(rt);
}

int query(int L,int R,int l,int r,int rt) {
    if(L <= l && r <= R) {
        return MIN[rt];
    }
    int ans = INF;
    int m = (l + r) >> 1;
    if(L <= m) ans = min(ans,query(L,R,lson));
    if(R > m) ans = min(ans,query(L,R,rson));
    return ans;
}

int pos[maxn << 2] , pre[maxn << 2];
int ans[maxn];

int main() {
    int n,Q;
    while(~scanf("%d%d",&n,&Q)) {
        memset(pos,-1,sizeof(pos));
        int sum = 0;
        pos[0] = 0;
        for(int i = 1 , x ; i <= n ; i++) {
            scanf("%d",&x);
            sum ^= x;
            if(pos[sum] != -1) pre[i] = pos[sum] + 1;
            else pre[i] = -1;
            pos[sum] = i;
        }
        for(int i = 1 ; i <= Q ; i++) {
            scanf("%d%d",&a[i].l,&a[i].r);
            a[i].id = i;
        }
        sort(a+1,a+Q+1,cmp);
        build(1,n,1);
        int cnt = 1;
        for(int i = 1 ; i <= n ; i++) {
            if(pre[i] != -1) {
                update(pre[i] , i - pre[i] + 1 , 1 , n , 1);
            }
            while(a[cnt].r == i) {
                ans[a[cnt].id] = query(a[cnt].l,a[cnt].r,1,n,1);
                cnt++;
            }
        }
        for(int i = 1 ; i <= Q ; i++) {
            printf("%d\n",ans[i] == INF ? -1 : ans[i]);
        }
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值