【BZOJ4137】火星商店問題(線段樹分治,可持久化Trie)
題面
洛谷
BZOJ權限題
題解
顯然可以樹套樹,外層線段樹,內層可持久化Trie來做。
所以我們需要更加優美的做法。——線段樹分治。
什么叫做線段樹分治呢?
我們發現每次詢問都是區間的形式,看到區間我們就可以想到線段數。
我們接着觀察,發現了兩個特點:詢問是區間,加入新數是單點。
那么我們對於單點構建線段樹,在本題中這個單點是按照時間順序構建的。
所以每次詢問在不考慮區間的情況下對應的時間是唯一的。
所以把每次詢問暴力放到線段樹的每個節點上,用\(vector\)記錄。
那么,我們\(dfs\)遍歷整棵線段樹,
我們發現每個詢問已經不用再考慮時間的問題,而只需要考慮區間的問題。
那么對於當前線段樹時間區間的所有插入操作按照區間排序。
構建可持久化\(Trie\)直接查答案就好了。
代碼應該注釋海星。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 100100
#define lson (now<<1)
#define rson (now<<1|1)
#define pb(x) push_back(x)
inline int read()
{
RG int x=0,t=1;RG 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 Buy{int s,v,t;}q[MAX],tmp1[MAX],tmp2[MAX];
struct Ask{int l,r,tl,tr,x;}p[MAX];
bool cmp(Buy a,Buy b){return a.s<b.s;}
int rt[MAX];
namespace Trie//可持久化Trie
{
struct Trie{int son[2],w;}t[MAX<<5];
int tot,rt[MAX];
void insert(int &x,int ff,int w,int now)
{
t[x=++tot]=t[ff];t[x].w++;
if(now==-1)return;
bool c=w&(1<<now);
insert(t[x].son[c],t[ff].son[c],w,now-1);
}
int Query(int l,int r,int w,int now)
{
if(now==-1)return 0;
bool c=w&(1<<now);
int tmp=t[t[r].son[c^1]].w-t[t[l].son[c^1]].w;
if(tmp)return Query(t[l].son[c^1],t[r].son[c^1],w,now-1)+(1<<now);
else return Query(t[l].son[c],t[r].son[c],w,now-1);
}
}
int n,m,ans[MAX];
vector<int> seg[MAX<<2];
int cnt1,cnt2;
//對於線段樹的每個節點插入對應的詢問
//線段樹的每個節點代表着一個購買的時間
//然后對於每個線段樹上的節點,維護哪些詢問出現在了這些時間之中
//所以對於一個節點維護一個vector,將出現在這段時間中的詢問放入vector中
void Modify(int now,int l,int r,int L,int R,int x)
{
if(L>R)return;
if(L<=l&&r<=R){seg[now].pb(x);return;}
int mid=(l+r)>>1;
if(L<=mid)Modify(lson,l,mid,L,R,x);
if(R>mid)Modify(rson,mid+1,r,L,R,x);
}
int S[MAX],top;
//對於當前節點計算在區間內的答案
//考慮如何計算貢獻,因為保證了當前節點內的所有詢問的時間
//所以只需要考慮區間的問題了,因此按照區間維護可持久化Trie即可
int Binary(int x)
{
int l=1,r=top,ret=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(S[mid]<=x)ret=mid,l=mid+1;
else r=mid-1;
}
return ret;
}
void Calc(int now,int L,int R)
{
top=Trie::tot=0;
for(int i=L;i<=R;++i)
{
S[++top]=q[i].s;
Trie::insert(rt[top],rt[top-1],q[i].v,17);
}
for(int i=0,len=seg[now].size();i<len;++i)
{
int k=seg[now][i];
int l=Binary(p[k].l-1),r=Binary(p[k].r);
ans[k]=max(ans[k],Trie::Query(rt[l],rt[r],p[k].x,17));
}
}
void Divide(int now,int l,int r,int L,int R)
{
if(L>R)return;int mid=(l+r)>>1,t1=0,t2=0;
Calc(now,L,R);if(l==r)return;
for(int i=L;i<=R;++i)
if(q[i].t<=mid)tmp1[++t1]=q[i];
else tmp2[++t2]=q[i];
for(int i=1;i<=t1;++i)q[i+L-1]=tmp1[i];
for(int i=1;i<=t2;++i)q[i+L-1+t1]=tmp2[i];
Divide(lson,l,mid,L,L+t1-1);
Divide(rson,mid+1,r,L+t1,R);
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;++i)Trie::insert(rt[i],rt[i-1],read(),17);
for(int i=1;i<=m;++i)
{
int opt=read();
if(!opt)
{
int s=read(),v=read();++cnt1;
q[cnt1]=(Buy){s,v,cnt1};
}
else
{
int l=read(),r=read(),x=read(),d=read();
ans[++cnt2]=Trie::Query(rt[l-1],rt[r],x,17);
p[cnt2]=(Ask){l,r,max(1,cnt1-d+1),cnt1,x};
}
}
for(int i=1;i<=cnt2;++i)Modify(1,1,cnt1,p[i].tl,p[i].tr,i);
sort(&q[1],&q[cnt1+1],cmp);//按照商店的編號依次插入所有物品
Divide(1,1,cnt1,1,cnt1);
for(int i=1;i<=cnt2;++i)printf("%d\n",ans[i]);
return 0;
}
