[BZOJ2333][SCOI2011]棘手的操作(可并堆||线段树)

来源:互联网 发布:哈密顿算法原理 编辑:程序博客网 时间:2024/06/01 09:27

=== ===

这里放传送门

=== ===

题解

这道题有两种做法:可并堆和线段树。相比于可并堆的写法来说线段树的写法非常简单并且好懂。。。然而这道题作为可并堆的练习也确实很有价值。。

首先提一句线段树的解法。可以发现它只要并到一起去的联通块是不会再拆开的,所以我们能够把每个时刻出现的连通块都标号为一个连续的区间,那么就可以用线段树进行区间修改区间查询。

操作方法是先把操作离线,然后对于每个合并操作用并查集来维护,为每个集合维护一个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;}

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

这题的线段树做法是很经典的思路啊
可并堆的做法就适合闲着没事的时候花上一天磨磨蹭蹭写着玩啊

0 0
原创粉丝点击