[BZOJ2333][SCOI2011]棘手的操作(可并堆||线段树)
2017-03-07 07:03
423 查看
=== ===
这里放传送门=== ===
题解
这道题有两种做法:可并堆和线段树。相比于可并堆的写法来说线段树的写法非常简单并且好懂。。。然而这道题作为可并堆的练习也确实很有价值。。首先提一句线段树的解法。可以发现它只要并到一起去的联通块是不会再拆开的,所以我们能够把每个时刻出现的连通块都标号为一个连续的区间,那么就可以用线段树进行区间修改区间查询。
操作方法是先把操作离线,然后对于每个合并操作用并查集来维护,为每个集合维护一个ed数组表示这一块的最后一个点的编号,再为每个点维护一个nxt值表示它的下一个节点编号。也就是说用并查集的father数组可以找到这个连通块的第一个节点,而ed数组可以找到最后一个,这就保证了节点的顺序。
在合并两个集合的时候按照顺序把一段节点接到另一段节点后面,这就要求在修改的时候一定要按顺序进行。并且这种操作方式决定了只有代表元素的ed值是正确的,因为每次修改的时候不能顺着并查集全改一遍不然肯定T死。
遍历所有点的时候每次遇到一个代表元素就用nxt数组遍历所有连通块然后依次标号就可以了。然后把father和ed数组初始化,重新做一边操作来处理询问就可以了。
对于可并堆来说的话就不需要离线直接在线处理就可以了。对于第一个操作就是直接合并两个堆,对于第二个操作它需要修改单个元素的值,那么这个可并堆必须支持找到某个元素的位置并且修改它,那么向上调整和向下调整两个操作都要搞出来;然后因为可并堆用指针记录了左右儿子和父亲的位置,相当于是搞了一个双向指针的东西,动了一个地方其它都跟着乱动就特烦人。。
因为第三个操作要修改整个连通块,所以要在可并堆里面维护lazy标记,每次merge操作的时候得先push一下把标记传下去。并且向上调整之前还要先把它祖先的标记都放下来。
最麻烦的操作就是整体最大值的维护。。因为编号是散乱的所以没法直接用线段树之类的东西来搞,所以就搞了个堆套堆。。似乎用STL也可以?这题ATP在WC的时候调了三天因为搞得断断续续所以也写得奇丑无比。。目测根本没法看qwq
代码
简洁明了的线段树版本#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,m,a[300010],father[300010],ed[300010],w[300010],num[300010],next[300010],cnt; int Max[1500000],dlt[1500000]; struct question{ int k,x,y; }q[300010]; char get(){ int c=getchar(); while (c>'Z'||c<'A') c=getchar(); return c; } int find(int x){ if (father[x]!=x) father[x]=find(father[x]); return father[x]; } void update(int i){ Max[i]=max(Max[i<<1],Max[(i<<1)+1]); } void pushdown(int i){ if (dlt[i]!=0){ Max[i<<1]+=dlt[i];Max[(i<<1)+1]+=dlt[i]; dlt[i<<1]+=dlt[i];dlt[(i<<1)+1]+=dlt[i]; dlt[i]=0; } } void build(int i,int l,int r){ if (l==r){ Max[i]=num[l];return; } int mid=(l+r)>>1; build(i<<1,l,mid); build((i<<1)+1,mid+1,r); update(i); } void change(int i,int l,int r,int left,int right,int v){ if (left<=l&&right>=r){ Max[i]+=v;dlt[i]+=v;return; } int mid=(l+r)>>1; pushdown(i); if (left<=mid) change(i<<1,l,mid,left,right,v); if (right>mid) change((i<<1)+1,mid+1,r,left,right,v); update(i); } int ask(int i,int l,int r,int left,int right){ if (left<=l&&right>=r) return Max[i]; int mid=(l+r)>>1,ans=-0x7fffffff; pushdown(i); if (left<=mid) ans=max(ans,ask(i<<1,l,mid,left,right)); if (right>mid) ans=max(ans,ask((i<<1)+1,mid+1,r,left,right)); return ans; } int main() { scanf("%d",&n); for (int i=1;i<=n;i++){ scanf("%d",&a[i]); father[i]=ed[i]=i; } scanf("%d",&m); for (int i=1;i<=m;i++){ char c=get(); if (c=='U'){ int r1,r2; q[i].k=1;scanf("%d%d",&q[i].x,&q[i].y); r1=find(q[i].x);r2=find(q[i].y); if (r1!=r2){//注意合并操作的顺序要求 father[r2]=r1;next[ed[r1]]=r2;ed[r1]=ed[r2]; } } if (c=='A'){ char z=getchar(); if (z=='3'){ q[i].k=4;scanf("%d",&q[i].x); }else{ q[i].k=z-'0'+1;scanf("%d%d",&q[i].x,&q[i].y); } } if (c=='F'){ char z=getchar(); if (z=='3') q[i].k=7; else {q[i].k=z-'0'+4;scanf("%d",&q[i].x);} } } for (int i=1;i<=n;i++) if (find(i)==i){ for (int j=i;j!=0;j=next[j]){ w[j]=++cnt;num[cnt]=a[j]; } } for (int i=1;i<=n;i++) father[i]=ed[i]=i; build(1,1,n); for (int i=1;i<=m;i++) switch (q[i].k){ case 1:{ int r1,r2; r1=find(q[i].x);r2=find(q[i].y); if (r1!=r2){ father[r2]=r1;next[ed[r1]]=r2;ed[r1]=ed[r2]; }//重新进行一遍操作 break; } case 2:{ change(1,1,n,w[q[i].x],w[q[i].x],q[i].y); break; } case 3:{ int r=find(q[i].x); change(1,1,n,w[r],w[ed[r]],q[i].y); break; } case 4:{ change(1,1,n,1,n,q[i].x); break; } case 5:{ printf("%d\n",ask(1,1,n,w[q[i].x],w[q[i].x])); break; } case 6:{ int r=find(q[i].x); printf("%d\n",ask(1,1,n,w[r],w[ed[r]])); break; } case 7:{ printf("%d\n",ask(1,1,n,1,n)); break; } } return 0; }
丑陋至极的可并堆版本
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,q,a[300010],delta,father[300010],ptr[300010]; struct Node{ Node *l,*r,*fa; int val,NPL,dlt; Node(); Node(int x); void count(){NPL=r->NPL+1;} void push(); void Add(int v); void pushdlt(); void faswap(); void pushup(int rt); void pushdown(int rt); }*null,H[300010],*h[300010],*st[300010]; struct Temp{ Node* *p; int id; }tmp[300010]; Node::Node(){l=r=fa=null;val=NPL=dlt=0;} Node::Node(int x){l=r=fa=null;val=x;NPL=1;dlt=0;} void Node::Add(int v){val+=v;dlt+=v;} void Node::push(){ if (dlt!=0){ if (l!=null) l->Add(dlt); if (r!=null) r->Add(dlt); dlt=0; } } void Node::pushdlt(){ Node *ptr=this; int top=0; while (ptr!=null){ st[++top]=ptr; ptr=ptr->fa; } for (int i=top;i>=1;i--) st[i]->push(); } void Node::faswap(){ Node *k=fa; if (this==k->l){ swap(r,k->r);k->l=l;l=k; }else{swap(l,k->l);k->r=r;r=k;} fa=k->fa; if (k==k->fa->l) k->fa->l=this; else k->fa->r=this; if (k->r!=null) k->r->fa=k; if (k->l!=null) k->l->fa=k; if (l!=null) l->fa=this; if (r!=null) r->fa=this; count();k->count(); } void Node::pushup(int rt){ Node *k=fa; if (k==null){h[rt]=this;return;} if (val<=k->val) return; faswap();pushup(rt); } void Node::pushdown(int rt){ int Maxs=-1; Node *tmp; if (this==null||(l==null&&r==null)) return; Maxs=max(l->val,r->val); l->push();r->push(); if (Maxs<=val) return; if (Maxs==l->val) l->faswap(); else r->faswap(); if (fa->fa==null) h[rt]=fa; pushdown(rt); } Node* merge(Node *x,Node *y){ if (x==null) return y; if (y==null) return x; if (x->val<y->val) swap(x,y); x->push(); x->r=merge(x->r,y); x->r->fa=x; if (x->l->NPL<x->r->NPL) swap(x->l,x->r); x->count();return x; } int comp(Temp x,Temp y){return (*(x.p))->val>(*(y.p))->val;} int find(int x){ if (father[x]==x) return father[x]; father[x]=find(father[x]); return father[x]; } int getnum(char x,char y){ int c; if (x=='U') return 1; if (x=='A') c=1; else c=4; return c+y-'0'; } void pushup(int now){ int fa=now>>1; if (fa==0||(*tmp[now].p)->val<=(*tmp[fa].p)->val) return; tmp[now].id=find(tmp[now].id); tmp[fa].id=find(tmp[fa].id); if (tmp[now].id!=0&&tmp[fa].id!=0) swap(ptr[tmp[now].id],ptr[tmp[fa].id]); swap(tmp[now],tmp[fa]); pushup(fa); } void pushdown(int now){ int Maxs=-0x7fffffff,l=now<<1,r=l+1,r1,r2,r3; if (l>n&&r>n) return; if (l<=n) Maxs=max(Maxs,(*tmp[l].p)->val); if (r<=n) Maxs=max(Maxs,(*tmp[r].p)->val); tmp[now].id=r1=find(tmp[now].id); tmp[l].id=r2=find(tmp[l].id); tmp[r].id=r3=find(tmp[r].id); if (r2==0&&r3==0) return; if (Maxs<=(*tmp[now].p)->val||r1==0) return; if (r2!=0&&Maxs==(*tmp[l].p)->val){ swap(ptr[r2],ptr[r1]); swap(tmp[l],tmp[now]); pushdown(l); }else if (r3!=0){ swap(ptr[tmp[r].id],ptr[tmp[now].id]); swap(tmp[r],tmp[now]); pushdown(r); } } void Union(int x,int y){ int r1=find(x),r2=find(y); if (r1==r2) return; tmp[ptr[r2]].p=&null; pushdown(ptr[r2]); tmp[ptr[r2]].id=ptr[r2]=0; father[r2]=r1; h[r1]=merge(h[r1],h[r2]); pushup(ptr[r1]); } void Addpoint(int x,int v){ int rt=find(x); H[x].pushdlt();//下放当前节点的标记 H[x].val+=v; if (v>0) H[x].pushup(rt); else H[x].pushdown(rt);//调整节点在当前堆里的位置 if (v<0) pushdown(ptr[rt]);//调整当前堆顶的位置 else pushup(ptr[rt]); } void Addblock(int x,int v){ int rt=find(x); h[rt]->Add(v); if (v<0) pushdown(ptr[rt]); else pushup(ptr[rt]); } int main() { null=new Node;*null=Node(); null->val=-0x7fffffff; scanf("%d",&n); for (int i=1;i<=n;i++){ scanf("%d",&a[i]); father[i]=i;H[i]=Node(a[i]); h[i]=H+i;tmp[i].p=h+i; tmp[i].id=i; } sort(tmp+1,tmp+n+1,comp); for (int i=1;i<=n;i++) ptr[tmp[i].id]=i; scanf("%d",&q); for (int i=1;i<=q;i++){ char s[5];scanf("%s",s); int opt,x,y,v,rt; opt=getnum(s[0],s[1]); switch (opt){ case 1:{ scanf("%d%d",&x,&y); Union(x,y);break; } case 2:{ scanf("%d%d",&x,&v); Addpoint(x,v);break; } case 3:{ scanf("%d%d",&x,&v); Addblock(x,v);break; } case 4:{ scanf("%d",&v); delta+=v;break; } case 5:{ scanf("%d",&x);H[x].pushdlt(); printf("%d\n",H[x].val+delta);break; } case 6:{ scanf("%d",&x);rt=find(x); printf("%d\n",h[rt]->val+delta);break; } case 7:{printf("%d\n",(*tmp[1].p)->val+delta);break;} } } return 0; }
偏偏在最后出现的补充说明
这题的线段树做法是很经典的思路啊可并堆的做法就适合闲着没事的时候花上一天磨磨蹭蹭写着玩啊
相关文章推荐
- 【BZOJ 2333 】[SCOI2011]棘手的操作(离线+线段树|可并堆-左偏树)
- 【BZOJ2333】【SCOI2011】棘手的操作
- BZOJ 2333 SCOI2011 棘手的操作 并查集+可并堆
- bzoj 2333: [SCOI2011]棘手的操作 离线+线段树
- 【BZOJ2333】棘手的操作(SCOI2011)-线段树+并查集+离线处理
- 【BZOJ2333】【SCOI2011】棘手的操作 可并堆+堆套堆(什么嘛,用个set就好啦)
- 【bzoj2333】[SCOI2011]棘手的操作 可并堆+set
- 【bzoj2333】 [SCOI2011]棘手的操作 可并堆+lazy标记
- bzoj 2333: [SCOI2011]棘手的操作(线段树+离线操作)
- (右偏树)Bzoj2333: [SCOI2011]棘手的操作
- BZOJ 2333: [SCOI2011]棘手的操作
- 【bzoj2333】[SCOI2011]棘手的操作 可并堆+STL-set
- BZOJ 2333 【SCOI2011】 棘手的操作
- BZOJ 2333 SCOI2011 棘手的操作 可并堆套可并堆
- [BZOJ2333][SCOI2011][可并堆]棘手的操作
- 【bzoj2333】 SCOI2011—棘手的操作
- (右偏树)Bzoj2333: [SCOI2011]棘手的操作
- 【BZOJ2333】【SCOI2011】棘手的操作 treap合并
- [可并堆] BZOJ 2333 [SCOI2011]棘手的操作
- [bzoj2333] [SCOI2011]棘手的操作 (可并堆)