【洛谷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;
}