[FJOI2015]火星商店问题(线段树分治+可持久化01Trie)

洛谷题目传送门

解题思路

看到区间异或最大值,我们可以想到可持久化Trie树
而看到动态的加入和删除,我们想到线段树分治
因此把这两个结合起来就行了
这个题和一般的线段树分治不太一样的的是,并不是物品有时间范围,而是查询有时间范围,我们知道最值问题是有可重叠性的,因此可以把每个询问的区间插入到时间线段树中,查询的时候
我们可以先对物品按照商店编号排序,保证建立可持久化Trie树的时候更容易
假设我们在线段树上遍历了某个区间,就把这个区间内的所有物品一起构建一颗可持久化0/1Trie,然后因为没有时间的限制,我们把这个区间内的询问的答案更新就行了
退出的时候为了减少撤销的复杂度,可以直接重构Trie树
复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
inline int read()
{
    int x=0,t=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
struct Sell
{
	int x,v,t;
}A[N],tmp[N];
struct Buy
{
	int l,r,tl,tr,x;
}Q[N];
bool cmp(Sell x,Sell y)
{
	return x.x<y.x;
}
int rot[N];
int trie[64*N][2];
int val[64*N];
int tot=0;
void Insert(int &x,int y,int w,int len)
{
	x=++tot;
	trie[x][0]=trie[y][0];
	trie[x][1]=trie[y][1];
	val[x]=val[y]+1;
	if(len==-1) return;
	bool c=(w&(1<<len));
	Insert(trie[x][c],trie[y][c],w,len-1);
	
}
int Ask(int l,int r,int w,int len)
{
	if(len==-1) return 0;
	bool c=(w&(1<<len));
	if(val[trie[r][c^1]]-val[trie[l][c^1]]>0) return Ask(trie[l][c^1],trie[r][c^1],w,len-1)+(1<<len);
	else return Ask(trie[l][c],trie[r][c],w,len-1);
}
int n,m,Ans[N];
vector<int> Query[N<<2];
int cnt1=0,cnt2=0;
void Add(int k,int l,int r,int L,int R,int x)
{
	if(L>R) return;
	if(L<=l&&r<=R)
	{
		Query[k].push_back(x);
		return;
	}
	int mid=(l+r)>>1;
	if(L<=mid) Add(k<<1,l,mid,L,R,x);
	if(R>mid) Add(k<<1|1,mid+1,r,L,R,x);
}
int s[N];
int top=0;
int Search(int x)
{
	int l=1,r=top,res=0;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(s[mid]<=x)
		{
			res=mid;
			l=mid+1;
		}
		else r=mid-1;
	}
	return res;
}
void Calc(int k,int L,int R)
{
	top=0;
	tot=0;
	for(int i=L;i<=R;i++)
	{
		s[++top]=A[i].x;
		Insert(rot[top],rot[top-1],A[i].v,17); 
	}
	for(int i=0;i<Query[k].size();i++)
	{
		int id=Query[k][i];
		int l=Search(Q[id].l-1),r=Search(Q[id].r);
		Ans[id]=max(Ans[id],Ask(rot[l],rot[r],Q[id].x,17));
	}
}
void solve(int k,int l,int r)
{
	int mid=(l+r)>>1;
	Calc(k,l,r);
	if(l==r) return;
	int topl=l,topr=mid+1;
	for(int i=l;i<=r;i++)
	{
		if(A[i].t<=mid) tmp[topl++]=A[i];
		else tmp[topr++]=A[i];
	}
	for(int i=l;i<=r;i++) A[i]=tmp[i];
	solve(k<<1,l,mid);
	solve(k<<1|1,mid+1,r);
}
int main()
{
	n=read();
	m=read();
	for(int i=1;i<=n;i++)
	{
		int x=read();
		Insert(rot[i],rot[i-1],x,17);
	}
	for(int i=1;i<=m;i++)
	{
		int opt=read();
		if(opt==0)
		{
			int x=read(),v=read();
			cnt1++;
			A[cnt1]=(Sell){x,v,cnt1};
		}
		else
		{
			int l=read(),r=read(),x=read(),d=read();
			Ans[++cnt2]=Ask(rot[l-1],rot[r],x,17);
			Q[cnt2]=(Buy){l,r,max(1,cnt1-d+1),cnt1,x};
		}
	}
	for(int i=1;i<=cnt2;i++) Add(1,1,cnt1,Q[i].tl,Q[i].tr,i);
	sort(A+1,A+cnt1+1,cmp);
	solve(1,1,cnt1);
	for(int i=1;i<=cnt2;i++)
	printf("%d\n",Ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值