[BZOJ3489]A simple rmq problem(可持久化树套树||K-D tree)

    xiaoxiao2021-03-25  103

    === ===

    这里放传送门

    === ===

    题解

    ATP当时是被zyf2000安利了这道题。。说是一个不错的数据结构题。。然后当时还不会K-D tree的ATP吭哧吭哧想了一晚上。。。然后就写出了主席树套树这种东西。。。后来ATP会了K-D tree以后又写了一发,发现madan用K-D tree真TM好写。。。

    这个题作为K-D tree的练习题还是不错的,因为它启发了ATP关于K-D tree的正确打开方式。。但是如果不用K-D tree这题就真是个大毒瘤了。。madan主席树套树啊这种东西是人写的吗。。。。

    主席树套树和K-D tree的思路有一部分是重叠的。都要考虑什么样的数字会对答案产生贡献。因为它要找只出现过一次的数字,所以可以想到能够预处理这个数字上一个出现的位置和下一个出现的位置pre和nxt。这个数会对当前询问[l,r]产生贡献当且仅当询问的区间[l,r]被开区间(pre,nxt)完全包含。那么可以用主席树的root[i]存储pre在1..i范围内的数字信息,然后里面的树套树一层是nxt在某段区间内的数字信息,一层是pre和nxt都满足限制并且位置在某段区间内的数字。这样就可以直接查询了。每次都是查出来当前一段区间内合法数字的最大值,所以答案可以轻易的合并。时间复杂度 O(nlog2n) 。随机数据的话空间也是 O(nlog2n) 的,大约开个 2.6107 就够了,但有些数据会把树套树的空间卡到 3.2107 个节点,所以空间上一定要注意。

    K-D树的话仍然是沿用上面利用pre和nxt来判断的思路,对于每一个数字有三重限制——pre,nxt和位置。那么就把这三个限制分别看成三维坐标,就变成了在三维空间中找某个范围内点权最大的点,用KD树就能十分方便的解决。然而有一种写法是把权值也看成一维坐标,这种做法似乎比三维坐标要快?

    代码

    可持久化树套树

    #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,m,cnt,tot,lastans,root[410000],tmp[100010]; struct number{ int val,pre,nxt,id; }a[100010]; int comp(number a,number b){return a.pre<b.pre;} struct segtree{ int l,r,Max; }t[35000000]; struct prstree{ int l,r,root; }rt[3000010]; void ins_itv(int &i,int l,int r,int x,int val,int fir){ if (i<fir||fir==-1){ t[++tot]=t[i];i=tot; }//如果有标记就更新,注意要继承前面的结果 t[i].Max=max(t[i].Max,val);//在当前节点上修改 if (l==r) return; int mid=(l+r)>>1; if (x<=mid) ins_itv(t[i].l,l,mid,x,val,fir); else ins_itv(t[i].r,mid+1,r,x,val,fir); } void insert(int &i,int j,int l,int r,number x,int fir){ int mark=rt[i].root; if (i<fir||fir==-1){ i=++cnt;rt[i]=rt[j];mark=-1; }//判断是否需要打第一次更新的标记 ins_itv(rt[i].root,1,n,x.id,x.val,mark); if (l==r) return; int mid=(l+r)>>1; if (x.nxt<=mid) insert(rt[i].l,rt[j].l,l,mid,x,fir); else insert(rt[i].r,rt[j].r,mid+1,r,x,fir); } int ask_itv(int i,int l,int r,int left,int right){ if (i==0) return 0; if (left<=l&&right>=r) return t[i].Max; int mid=(l+r)>>1,ans=0; if (left<=mid) ans=max(ans,ask_itv(t[i].l,l,mid,left,right)); if (right>mid) ans=max(ans,ask_itv(t[i].r,mid+1,r,left,right)); return ans; } int ask(int i,int l,int r,int left,int right,int lpos,int rpos){ if (i==0) return 0; if (left<=l&&right>=r) return ask_itv(rt[i].root,1,n,lpos,rpos); int mid=(l+r)>>1,ans=0; if (left<=mid) ans=max(ans,ask(rt[i].l,l,mid,left,right,lpos,rpos)); if (right>mid) ans=max(ans,ask(rt[i].r,mid+1,r,left,right,lpos,rpos)); return ans; } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%d",&a[i].val); for (int i=1;i<=n;i++){ a[i].pre=tmp[a[i].val]+1; tmp[a[i].val]=i; a[i].id=i; } for (int i=1;i<=n;i++) tmp[i]=n+1; for (int i=n;i>=1;i--){ a[i].nxt=tmp[a[i].val]+1; tmp[a[i].val]=i; } sort(a+1,a+n+1,comp); for (int i=1,ptr=1;i<=n+2;i++){ int tail=ptr; root[i]=root[i-1];//继承前面的结果 if (a[ptr].pre!=i) continue; while (tail<n&&a[tail+1].pre==a[i].pre) ++tail; insert(root[i],root[i-1],1,n+2,a[ptr],-1); for (int j=ptr+1;j<=tail;j++) insert(root[i],root[i-1],1,n+2,a[j],root[i]); ptr=tail+1; } for (int i=1;i<=m;i++){ int l,r,x,y; scanf("%d%d",&x,&y); x=(x+lastans)%n+1; y=(y+lastans)%n+1; l=min(x,y);r=max(x,y); l++;r++; lastans=ask(root[l-1],1,n+2,r+1,n+2,l-1,r-1);//最后两个参数要带入原本的l和r printf("%d\n",lastans); } return 0; }

    K-D tree

    #include<cstdio> #include<cstring> #include<algorithm> #define N 3 using namespace std; int m,n,a[100010],pre[100010],nxt[100010],D,lastans,L[3],R[3],ans; struct Node{ Node *ch[2]; int d[3],Max[3],Min[3],val,Maxv; Node(); Node(int x,int y,int z,int v); void count(); }*null,*Root,t[100010]; Node::Node(){ ch[0]=ch[1]=null; for (int i=0;i<N;i++) d[i]=Max[i]=Min[i]=0; val=Maxv=0; } Node::Node(int x,int y,int z,int v){ ch[0]=ch[1]=null; d[0]=Max[0]=Min[0]=x; d[1]=Max[1]=Min[1]=y; d[2]=Max[2]=Min[2]=z; val=Maxv=v; } void Node::count(){ if (ch[0]!=null){ Maxv=max(Maxv,ch[0]->Maxv); for (int i=0;i<N;i++){ Max[i]=max(Max[i],ch[0]->Max[i]); Min[i]=min(Min[i],ch[0]->Min[i]); } } if (ch[1]!=null){ Maxv=max(Maxv,ch[1]->Maxv); for (int i=0;i<N;i++){ Max[i]=max(Max[i],ch[1]->Max[i]); Min[i]=min(Min[i],ch[1]->Min[i]); } } } int comp(Node a,Node b){ if (a.d[D]<b.d[D]) return true; if (a.d[D]>b.d[D]) return false; for (int i=0;i<N;i++) if (i!=D){ if (a.d[i]<b.d[i]) return true; if (a.d[i]>b.d[i]) return false; } return false; } void GetPreNxt(){ int tmp[100010]; memset(tmp,0,sizeof(tmp)); for (int i=1;i<=n;i++){ pre[i]=tmp[a[i]];tmp[a[i]]=i; } for (int i=1;i<=n;i++) tmp[i]=n+1; for (int i=n;i>=1;i--){ nxt[i]=tmp[a[i]];tmp[a[i]]=i; } } Node* build(int l,int r,int d){ if (l>r) return null; int mid=(l+r)>>1;D=d; nth_element(t+l,t+mid,t+r+1,comp); d=d%N+1; t[mid].ch[0]=build(l,mid-1,d); t[mid].ch[1]=build(mid+1,r,d); t[mid].count();return t+mid; } bool check(int *d){ for (int i=0;i<N;i++) if (d[i]<L[i]||d[i]>R[i]) return false; return true; } int calc(Node *now){ if (now==null) return 0; if (now->Max[0]<L[0]||now->Min[0]>R[0]) return 0; if (now->Min[1]>R[1]||now->Max[2]<L[2]) return 0; return 1; } void Query(Node *now){ int d0,dl,dr; Node *l,*r; if (now==null) return; if (check(now->d)) ans=max(ans,now->val); dl=calc(now->ch[0]);l=now->ch[0]; dr=calc(now->ch[1]);r=now->ch[1]; if (l->Maxv<r->Maxv){swap(l,r);swap(dl,dr);} if (l!=null&&dl!=0&&l->Maxv>ans) Query(l); if (r!=null&&dr!=0&&r->Maxv>ans) Query(r); } int main() { null=new Node;*null=Node(); scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%d",&a[i]); GetPreNxt(); for (int i=1;i<=n;i++) t[i]=Node(i,pre[i],nxt[i],a[i]); Root=build(1,n,0); for (int i=1;i<=m;i++){ int l,r;scanf("%d%d",&l,&r); l=(l+lastans)%n+1; r=(r+lastans)%n+1; if (l>r) swap(l,r); L[0]=l;R[0]=r; L[1]=0;R[1]=l-1; L[2]=r+1;R[2]=n+1; ans=0;Query(Root); lastans=ans; printf("%d\n",ans); } return 0; }

    偏偏在最后出现的补充说明

    K-D tree不是光用来解决平面最近点对什么的问题的。。。很多问题转化一下都可以用K-D tree方便地解决。 话说K-D tree的代码怎么比主席树套树还要长啊。。。。

    转载请注明原文地址: https://ju.6miu.com/read-21729.html

    最新回复(0)