关于treap启发式合并的一点脑洞(以bzoj2809为例)
2017-01-30 22:28
323 查看
首先我知道bzoj2809正解应该是可并堆,之所以写treap启发式合并单纯只是因为这个脑洞…
首先我们有两个treap,分别是 A 和 B ,它们的节点数分别为 n 和 m (n<m)。
网上普通的启发式合并是把 A 中的结点一个个扔进 B 里,这样复杂度是 O(nlogm) 的。
然而 zyqn 告诉我 myy 说这个事情可以做到 O(nlogmn) ,这就非常一颗赛艇了。
因为这意味着,如果你有 n 个只有一个结点的 treap,然后我每次指定两个让你合并,顺便询问询问,最后合并成一个 treap, 那么这个事情可以做到 O(nlogn) 而不是 O(nlog2n) 。
然后我搞了搞发现我可能写挂了,不过还是决定放上来讲讲。
具体来说是把 B 那个 treap 拆成一个序列,然后塞进 A 相应的地方。就像这样:
![](https://img-blog.csdn.net/20170130221441896?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2Nwb2ludGVy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
但是光这么搞会出一些问题,比如有 n 个结点,权值分别为 1~n ,那把第二个,第三个…第 n 个点依次塞进第一个点的 treap 里,这个 treap 就会变成一条链。补救办法是在某些时候把需要 merge 的 A 和 B 两个 treap 都拆成序列,然后暴力重构整个 treap 的这个子树。
然后问题就是什么时候暴力重构了,这个可以参考普通 treap,如果两个 treap 的结点数分别为 n 和 m (n<m),那么我们以 nn+m 的概率重构它。还有如果最开始被拆成序列的那个 treap 如果在 merge 到某个点的时候,序列长度比当前没被拆的那个 treap 的大小要大了,那暴力重构肯定不会影响复杂度。
这么一通搞完以后我发现复杂度我并不会证。也许是 O(nlogmn) ,也许还是 O(nlogm)。
不过这样写有一个好,就是如果这个 treap 不需要可持久化的话,空间复杂度是 O(总节点数) 的,不会出现卡空间的问题。但是要多写这么多函数啊喂,如果是OI比赛调不出来怎么办
附代码
首先我们有两个treap,分别是 A 和 B ,它们的节点数分别为 n 和 m (n<m)。
网上普通的启发式合并是把 A 中的结点一个个扔进 B 里,这样复杂度是 O(nlogm) 的。
然而 zyqn 告诉我 myy 说这个事情可以做到 O(nlogmn) ,这就非常一颗赛艇了。
因为这意味着,如果你有 n 个只有一个结点的 treap,然后我每次指定两个让你合并,顺便询问询问,最后合并成一个 treap, 那么这个事情可以做到 O(nlogn) 而不是 O(nlog2n) 。
然后我搞了搞发现我可能写挂了,不过还是决定放上来讲讲。
具体来说是把 B 那个 treap 拆成一个序列,然后塞进 A 相应的地方。就像这样:
但是光这么搞会出一些问题,比如有 n 个结点,权值分别为 1~n ,那把第二个,第三个…第 n 个点依次塞进第一个点的 treap 里,这个 treap 就会变成一条链。补救办法是在某些时候把需要 merge 的 A 和 B 两个 treap 都拆成序列,然后暴力重构整个 treap 的这个子树。
然后问题就是什么时候暴力重构了,这个可以参考普通 treap,如果两个 treap 的结点数分别为 n 和 m (n<m),那么我们以 nn+m 的概率重构它。还有如果最开始被拆成序列的那个 treap 如果在 merge 到某个点的时候,序列长度比当前没被拆的那个 treap 的大小要大了,那暴力重构肯定不会影响复杂度。
这么一通搞完以后我发现复杂度我并不会证。也许是 O(nlogmn) ,也许还是 O(nlogm)。
不过这样写有一个好,就是如果这个 treap 不需要可持久化的话,空间复杂度是 O(总节点数) 的,不会出现卡空间的问题。但是要多写这么多函数啊喂,如果是OI比赛调不出来怎么办
附代码
#include <cstdio> #include <algorithm> #define N 100050 #define NDS 1000050 #define INF 1000000001 #define swap(a,b) std::swap(a,b) //namespace fastin //{ #define BUF 100005 char __s[BUF],*_ss,*_st; inline char gchar() { if(_ss==_st) _st=(_ss=__s)+fread(__s,sizeof(char),BUF-5,stdin); return *_ss++; } inline int RD() { int res;char cr; while( (cr=gchar())<'0' || cr>'9');res=cr-'0'; while( (cr=gchar())>='0' && cr<='9') res=res*10+cr-'0'; return res; } #undef BUF //} int trand() { static long long x=2947183,a=3798293,b=1000000009 b530 ; return x=(x*a+b)&0x7fffffff; } int trand(int lim){return trand()%lim;} #define ls p->son[0] #define rs p->son[1] struct Node *null; struct Node{ int sz,val; long long totval; Node *son[2]; void update() { sz=(son[0]->sz)+(son[1]->sz)+1; totval=(son[0]->totval)+(son[1]->totval)+val; if(totval>INF) totval=INF; } }_pol[NDS],*_ndcnt; Node* newNode(){return _ndcnt++;} void init() { null=new Node(); *null=(Node){0,0,0,{NULL,NULL}}; _ndcnt=_pol; } Node *que ,*q2 ; int ql,qr,q2top; void takeApart(Node *p)//把treap p拆成序列 { if(p==null) return; takeApart(ls); que[++qr]=p; takeApart(rs); } void combine(Node *p,int &l,int r)//把treap p拆成序列,并且和que里的序列合成一个新序列 { if(p==null) return; combine(ls,l,r); while(l<=r && (que[l]->val)<(p->val)) q2[++q2top]=que[l++]; q2[++q2top]=p; combine(rs,l,r); } Node* rebuild(int l,int r)//暴力重建,把序列变成一个treap { if(l>r) return null; int mid=(l+r)>>1; // Node *p=newNode();*p=*q2[mid]; Node *p=q2[mid]; ls=rebuild(l,mid-1); rs=rebuild(mid+1,r); p->update(); return p; } Node* rebuild(Node *p,int l,int r)//暴力重建treap p和序列que(中的[l,r]这一段) { q2top=0; combine(p,l,r); while(l<=r) q2[++q2top]=que[l++]; return rebuild(1,q2top); } int find(int l,int r,int val) { //判定merge下传的时候应该把que序列中的前一半扔给左儿子,后一半扔给右儿子 //这个函数是用来找前一半和后一半的分界点的 //不要问我为什么不直接二分 for(int del=0;;del++) { if(val<(que[l+del]->val)) return l+del-1; if(val>=(que[r-del]->val)) return r-del; } } Node* merge(Node *rt,int l,int r) { //合并treap rt和序列que(中的[l,r]这一段)。这个代码的核心。 // Node *p=newNode();*p=*rt; Node *p=rt; if(l>r) return p; if(p==null) return rebuild(p,l,r); int size1=p->sz,size2=r-l+1; if(size1<=size2 || trand(size1+size2)<size2) return rebuild(p,l,r); int mid=find(l,r,p->val); ls=merge(rt->son[0],l,mid); rs=merge(rt->son[1],mid+1,r); p->update(); return p; } Node* merge(Node *a,Node *b)//合并a,b两个treap,其中b被拆成序列 { if((a->sz)<(b->sz)) swap(a,b); ql=1;qr=0; takeApart(b); return merge(a,ql,qr); } /*int query(Node* p,int maxcost) { if(p==null) return 0; if(p->totval<=maxcost) return p->sz; if(ls->totval<=maxcost) { if((ls->totval)+(p->val)<=maxcost) return ls->sz+1+query(rs,(maxcost)-(ls->totval)-(p->val)); else return ls->sz; } else return query(ls,maxcost); } */ int query(Node* &p,int maxcost)//本来应该用上面那个query(),这个函数是用题目性质加了一些常数优化 { if(p==null) return 0; if(p->totval<=maxcost) return p->sz; int res; if(ls->totval<=maxcost) { if((ls->totval)+(p->val)<=maxcost) res=ls->sz+1+query(rs,(maxcost)-(ls->totval)-(p->val)); else res=ls->sz,p=ls; } else res=query(ls,maxcost),p=ls; if(p!=null) p->update(); return res; } int fa ,cost ,leadership ; int fson ,bro ; Node *rot ; int main() { init(); int n,maxcost;n=RD();maxcost=RD(); for(int i=1;i<=n;i++) { fa[i]=RD();cost[i]=RD();leadership[i]=RD(); bro[i]=fson[fa[i]]; fson[fa[i]]=i; } long long ans=0; for(int i=n;i>=1;i--) { rot[i]=newNode(); *rot[i]=(Node){1,cost[i],cost[i],{null,null}}; for(int j=fson[i];j;j=bro[j]) rot[i]=merge(rot[i],rot[j]); long long res=(long long)leadership[i]*query(rot[i],maxcost); if(res>ans) ans=res; } printf("%lld\n",ans); }
相关文章推荐
- treap启发式合并(BZOJ2809)||splay||左偏树
- BOZJ2733 [HNOI2012]永无乡(Treap+启发式合并)
- BZOJ 2733 HNOI2012 永无乡 Treap+启发式合并
- CSU 1811 Tree Intersection(Treap启发式合并)
- treap启发式合并
- 【BZOJ 2733】[HNOI2012]永无乡 启发式合并treap
- 【bzoj2733】【HNOI2012】【永无乡】【treap+启发式合并】
- 【bzoj2733】[HNOI2012]永无乡 Treap启发式合并
- 2809: [Apio2012]dispatching 启发式合并treap 可并堆
- USACO 2013open :yinyang(treap+启发式合并)
- BZOJ 2733([HNOI2012]永无乡-Treap启发式合并)
- 【楼天城男人八题】【树分治|Treap+启发式合并】POJ1741 Tree
- 3545: [ONTAK2010]Peaks 启发式合并treap 离线处理
- Poj 1741——treap的启发式合并
- BZOJ 2809 APIO2012 dispatching Treap+启发式合并 / 可并堆
- BZOJ 2733: [HNOI2012]永无乡 启发式合并treap
- BZOJ 2733: [HNOI2012]永无乡 (Treap+启发式合并)
- BZOJ 2733: [HNOI2012]永无乡(treap + 启发式合并 + 并查集)
- 【BZOJ 2733】【HNOI 2012】永无乡【treap启发式合并】
- 关于INDEX SERVER+ASP建立查询引擎的一点心得