把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷5607】[Ynoi2013] 无力回天NOI2017(线段树套线性基)

点此看题面

大致题意: 给定一个序列,要求支持两种操作:区间修改,异或一个数;求在一段区间中选任意个数与询问值\(v\)异或得到的最大值。

线性基

看看这道题的询问,你想到了什么?

显然是线性基的经典操作嘛!

这其实也就启示了我们,只要想办法维护出一段区间的线性基,我们就能轻松地实现询问了。

可是,维护一段区间的线性基,又谈何容易?

让我们来看看题面,区间修改?带修线性基?线性基还能这么玩?

于是一脸懵逼的我开始了满脑子懵逼的思考,最后懵逼地无力回天。

线段树套线性基

考虑是要维护一段区间的线性基,而我们知道,线性基的合并是可以在\(O(log^2V)\)的复杂度内实现的。

而且,修改一般的线性基是非常困难的,但如果修改只有一个元素的线性基呢?

好了,话都说到这里了,不难明白,我所说的,正是线段树套线性基。

这样的复杂度是\(O(NlogNlog^2V)\)的。(\(CJJ\)\(log\)\(T\)的哒?)

等等,这样子似乎还是不能实现区间修改啊?

因此,我们需要把区间修改转化为单点修改。

怎么转化呢?这就需要利用异或这种神奇运算的神奇性质了。

我们令\(s_i=a_i\ xor\ a_{i-1}\),然后我们发现,对于\(i=l+1,l+2,...,r\)\(s_i\)都不改变。而发生变化的仅仅是\(s_l\)\(s_{r+1}\)两个值而已,这样一来就把区间修改转化为了单点修改。

再等等,这么一搞之后,还怎么处理询问呢?

然后我们发现,\(\{a_l,a_{l+1},...,a_r\}\)\(\{a_l,s_{l+1},s_{l+2},...,s_r\}\)二者的线性基是等价的。

为什么?因为\(s_i=a_i\ xor\ a_{i-1}\),所以后者其实还是\(\{a_l,a_{l+1},...,a_r\}\)这堆东西在异或来异或去,并没有少掉谁,也没有多冒出来谁。

所以,我们用线段树维护每个区间内\(s_i\)的线性基,询问时求出\(\{s_{l+1},s_{l+2},...,s_r\}\)的线性基,然后往里面扔入一个\(a_l\),我们就可以愉快地询问了。

最后一次等等,这样不是还要维护\(a_l\)吗?

是的,不过如果我们维护\(s_i\),然后就会发现\(xor_{i=1}^ls_i=(xor_{i=1}^la_i)\ xor\ (xor_{i=1}^{l-1}a_i)=a_l\)

也就是说,只要实现单点修改以及求前缀和就可以了,树状数组显然可以很优秀地完成这项任务。

写到这里,突然感觉维护线性基的线段树也可以直接借来做这个东西,反正都已经维护s了?算了,管它呢,反正写都写了,过都过了,还有啥好改的。

具体实现详见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
#define LV 30
using namespace std;
int n,a[N+5];
class LinearBasis//线性基
{
	private:
		#define LB LinearBasis
		int v[LV+5];
	public:
		I LB() {for(RI i=LV;~i;--i) v[i]=0;}
		I void Ins(RI x) {for(RI i=LV;~i;--i) if((x>>i)&1) {if(!v[i]) return (void)(v[i]=x);x^=v[i];}}//插入
		I int Qry(RI x) {for(RI i=LV;~i;--i) (x^v[i])>x&&(x^=v[i]);return x;}//查询
		I friend LB operator + (LB x,LB y) {for(RI i=LV;~i;--i) y.v[i]&&(x.Ins(y.v[i]),0);return x;}//合并
};
class SegmentTree//线段树
{
	private:
		#define PT CI l=1,CI r=n,CI rt=1
		#define LT l,mid,rt<<1
		#define RT mid+1,r,rt<<1|1 
		#define PU(x) (S[x]=S[x<<1]+S[x<<1|1])
		int V[N<<2];LB S[N<<2];
	public:
		I void Build(int *a,PT)//建树
		{
			if(l==r) return S[rt].Ins(V[rt]=a[l]^a[l-1]);int mid=l+r>>1;
			Build(a,LT),Build(a,RT),PU(rt);
		}
		I void U(CI x,CI v,PT)//单点修改
		{
			if(l==r) return (S[rt]=LB()).Ins(V[rt]^=v);int mid=l+r>>1;
			x<=mid?U(x,v,LT):U(x,v,RT),PU(rt);
		}
		I LB Q(CI x,CI y,PT)//求出一段区间的线性基
		{
			if(x<=l&&r<=y) return S[rt];int mid=l+r>>1;
			if(y<=mid) return Q(x,y,LT);if(x>mid) return Q(x,y,RT);
			return Q(x,mid,LT)+Q(mid+1,y,RT);
		}
}S;
class TreeArray//树状数组
{
	private:
		int a[N+5];
	public:
		I void U(RI x,CI v) {W(x<=n) a[x]^=v,x+=x&-x;}//单点修改
		I int Q(RI x,RI t=0) {W(x) t^=a[x],x-=x&-x;return t;}//求前缀和
}T;
int main()
{
	RI Qt,i,op,x,y,v;scanf("%d%d",&n,&Qt);
	for(i=1;i<=n;++i) scanf("%d",a+i),T.U(i,a[i]^a[i-1]);
	LB t;S.Build(a);W(Qt--) switch(scanf("%d%d%d%d",&op,&x,&y,&v),op)
	{
		case 1:S.U(x,v),T.U(x,v),y^n&&(S.U(y+1,v),T.U(y+1,v),0);break;//修改
		case 2:(t=x^y?S.Q(x+1,y):LB()).Ins(T.Q(x)),printf("%d\n",t.Qry(v));break;//询问
	}return 0;
}
posted @ 2020-05-11 19:35  TheLostWeak  阅读(325)  评论(0编辑  收藏  举报