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

=== ===

这里放传送门

=== ===

题解

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

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

操作方法是先把操作离线,然后对于每个合并操作用并查集来维护,为每个集合维护一个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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值