题解 [LuoguP5607][Ynoi2013] 无力回天 NOI2017

题解 无力回天 NOI2017

题目链接

询问最大 xor ⁡ \operatorname{xor} xor 和,支持修改,想到线段树套线性基。

但是操作一又做不了:打不了 tag。为什么打不了 tag 呢?因为 tag 只能记录一个数,而线段树维护的是线性基而不是异或和,如果打标记的话要打上很多数。

所以只能单点修改。

考虑用线段树维护差分数组,然后操作二又怎么做呢?

设差分数组为 b b b,如果我们要查询 [ a l , a r ] [a_l,a_r] [al,ar] 的线性基(设其为线性基一),可以查询 a l , [ b l + 1 , b r ] a_l,[b_{l+1},b_r] al,[bl+1,br] 的线性基(设其为线性基二)。

接下来证明二线性基等价:

若在线性基二中选择 b k b_k bk,可在线性基一中选择 a k − 1 , a k a_{k-1},a_k ak1,ak;若在线性基一种选择 a k a_k ak,如果 k = l k=l k=l,在线性基二中选择 a l a_l al,如果 k ≠ l k≠l k=l,可以选择 a l , [ b l + 1 , b k ] a_l,[b_{l+1},b_k] al,[bl+1,bk]感性理解一下。

所以我们使用线段树维护 b b b 数组。但是还有个 a l a_l al 怎么处理呢?想想我们会怎么操作 a a a 数组:区间修,单点查。所以用线段树和树状数组维护都可以。如果用树状数组维护则需维护差分数组。

注意:差分数组要从后往前算。

注意:线段树单点修改到叶子节点时,线性基要进行重构,此时可以顺便维护下差分数组,如要使第 k k k 位异或,令 a k = a k xor ⁡ v a_k=a_k \operatorname{xor} v ak=akxorv,再将此数插入线性基。

所以现在有: a a a 数组维护差分,树状数组维护差分的前缀和即原数,线段树维护线性基。问题解决了!

//P5607
#include <bits/stdc++.h>
using namespace std;

const int N = 5e4 + 10;
int n, m, a[N], bit[N];

struct node{
	int p[32], l, r;
	void ins(int k){
		for(int i = 31; i >= 0; -- i){
			if(!(k&(1<<i))) continue;
			if(!p[i]){ p[i] = k; break; }
			k ^= p[i];
		}
	}
	void ins(node &k){
		for(int i = 31; i >= 0; -- i) ins(k.p[i]);
	}
	int mx(int v){
		for(int i = 31; i >= 0; -- i) v = max(v, v ^ p[i]);
		return v;
	}
} T[N<<2], res;
struct SegTree{
	void buildtree(int p, int l, int r){
		T[p].l = l, T[p].r = r;
		if(l == r) T[p].ins(a[l]);
		else{
			int mid = l + r >> 1;
			buildtree(p<<1, l, mid);
			buildtree(p<<1|1, mid+1, r);
			T[p].ins(T[p<<1]); T[p].ins(T[p<<1|1]);
		}
	}
	void modify(int p, int x, int v){
		if(T[p].l == T[p].r){
			memset(T[p].p, 0, sizeof(T[p].p));
			T[p].ins(a[T[p].l] ^ v);
			a[T[p].l] ^= v;
		} else {
			int mid = T[p].l + T[p].r >> 1;
			if(x <= mid) modify(p<<1, x, v);
			else modify(p<<1|1, x, v);
			memset(T[p].p, 0, sizeof(T[p].p));
			T[p].ins(T[p<<1]); T[p].ins(T[p<<1|1]);
		}
	}
	void query(int p, int l, int r){
		if(l <= T[p].l && T[p].r <= r) res.ins(T[p]);
		else {
			int mid = T[p].l + T[p].r >> 1;
			if(l <= mid) query(p<<1, l, r);
			if(mid < r) query(p<<1|1, l, r);
		}
	}
} ST;
struct BIT{
	int lb(int x){ return x & -x; }
	void add(int p, int v){
		while(p <= n) bit[p] ^= v, p += lb(p);
	}
	int pre(int r){
		int ans = 0;
		while(r) ans ^= bit[r], r -= lb(r);
		return ans;
	} 
} BIT;
int main(){
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
	for(int i = n; i; -- i) a[i] ^= a[i-1];
	for(int i = 1; i <= n; ++ i) BIT.add(i, a[i]);
	ST.buildtree(1, 1, n);
	for(int i = 1, op, l, r, v; i <= m; ++ i){
		scanf("%d%d%d%d", &op, &l, &r, &v);
		if(op == 1){
			ST.modify(1, l, v), BIT.add(l, v);
			if(r < n) ST.modify(1, r+1, v), BIT.add(r+1, v);
		} else {
			memset(res.p, 0, sizeof(res.p));
			res.ins(BIT.pre(l)); ST.query(1, l+1, r);
			printf("%d\n", res.mx(v));
		}
	}
	return 0;
}

怎么说呢,至少也是一道 Ynoi,虽然思维难度可能比别的 Ynoi 简单一点,但是细节是一点也不少的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值