【BZOJ4184】shallot(线性基+线段树分治)

点此看题面

大致题意: 让你支持三种操作:加入一个数,删除一个数,求选出任意数的最大异或和。

动态线性基

首先看到选出任意数的最大异或和,肯定会想到线性基。

然后再看看它要我们支持加入和删除,加入还好,删除是什么鬼东西?

于是就需要请出我们的动态线性基了。

实际上,根据常规套路,很多动态的问题都可以转化为离线搞,这题也不例外。

线段树分治

线段树分治是一个很有意思的算法。

这题中,我们可以离线求出每个数的出现时间区间,然后对线段树上表示这一区间的节点们开个\(vector\)扔入这个数。

询问时我们遍历线段树,对每一层开一个线性基,每个线性基从父节点那里继承过来,然后再往线性基中插入该节点上的数。

容易发现每个叶节点上维护的正是这一时间存在的所有数。

于是这道题就做完了,代码也是很简洁的。

代码

#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 500000
#define LN 25
#define LV 30
using namespace std;
int n;map<int,int> p;
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define tn (x<<3)+(x<<1)
		#define D isdigit(c=tc())
		int f,T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0,f=1;W(!D) f=c^'-'?1:-1;W(x=tn+(c&15),D);x*=f;}
		Tp I void writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('\n');}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
class LinearBasis//线性基
{
	private:
		int v[LV+5];
	public:
		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 t=0) {for(RI i=LV;~i;--i) t<(t^v[i])&&(t^=v[i]);return t;}//询问
};
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
		LinearBasis B[LN+5];vector<int> V[N<<2];vector<int>::iterator it;
	public:
		I void Add(CI x,CI L,CI R,PT)//往一个区间内扔入x
		{
			if(L<=l&&r<=R) return V[rt].push_back(x);int mid=l+r>>1;
			L<=mid&&(Add(x,L,R,LT),0),R>mid&&(Add(x,L,R,RT),0);
		}
		I void Solve(CI d=0,PT)//线段树分治
		{
			for(it=V[rt].begin();it!=V[rt].end();++it) B[d].Ins(*it);//插入该节点上的数
			if(l==r) return F.writeln(B[d].Qry());int mid=l+r>>1;//边界直接询问输出
			B[d+1]=B[d],Solve(d+1,LT),B[d+1]=B[d],Solve(d+1,RT);//把线性基继承给儿子
		}
}S;
int main()
{
	RI i,x;for(F.read(n),i=1;i<=n;++i) F.read(x),x>0?p[x]=i:(x=-x,S.Add(x,p[x],i-1),p.erase(x),0);//开map记录一个数上次出现位置
	for(map<int,int>::iterator it=p.begin();it!=p.end();++it) S.Add(it->first,it->second,n);//把没被删掉过的数扔入线段树
	return S.Solve(),F.clear(),0;
}
上一篇:Reading Comprehensive


下一篇:第二类斯特林数·行