zoukankan      html  css  js  c++  java
  • BZOJ2001 HNOI2010 城市建设

      题目大意:动态最小生成树,可以离线,每次修改后回答,点数20000,边和修改都是50000。

     

      顾昱洲是真的神:顾昱洲_浅谈一类分治算法

      链接: https://pan.baidu.com/s/1c2lkayO 密码: 83rx

      讲的很妙,大致的几个注意点在代码里面也有提到。

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <queue>
    #include <algorithm>
    #include <cstring>
    #define LL long long
    using namespace std;
    
    const int N = 50010;
    const LL Inf = 1e9+7;
    struct UPD{int k,v;}Upd[N];
    struct EDGE{
      int x,y,pos,val;
      bool operator <(const EDGE &e)const{
        return val<e.val;
      }
    }E[51][N],Edge[N],que[N];
    int n,m,q,fa[N],pos[N],Enum[N],Eval[N];
    LL Ans[N];
    
    inline int gi(){
      int x=0,res=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
      while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
      return x*res;
    }
    
    inline int find(int x){
      return fa[x]==x?x:fa[x]=find(fa[x]);
    }
    
    inline void clear(int tot){
      for(int i=1;i<=tot;++i){
        fa[Edge[i].x]=Edge[i].x;
        fa[Edge[i].y]=Edge[i].y;
      }
    }
    
    inline void contraction(int &tot,LL &tval,int cnt=0){//求出必在树中的边,操作是缩点+永久修改总代价
      clear(tot);sort(Edge+1,Edge+tot+1);
      for(int i=1;i<=tot;++i){
        int f1=find(Edge[i].x),f2=find(Edge[i].y);
        if(f1==f2)continue;
        fa[f2]=f1;que[++cnt]=Edge[i];
      }
      for(int i=1;i<=cnt;++i)
        fa[que[i].x]=que[i].x,fa[que[i].y]=que[i].y;
      for(int i=1;i<=cnt;++i){
        if(que[i].val==-Inf)continue;
        int f1=find(que[i].x),f2=find(que[i].y);
        fa[f2]=f1;tval+=que[i].val;
      }
      cnt=0;
      for(int i=1;i<=tot;++i){
        int f1=find(Edge[i].x),f2=find(Edge[i].y);
        if(f1==f2)continue;
        que[++cnt]=Edge[i];pos[Edge[i].pos]=cnt;
        que[cnt].x=f1;que[cnt].y=f2;
      }
      tot=cnt;for(int i=1;i<=tot;++i)Edge[i]=que[i];
      //上面的操作是求出在树中的、代价不为-Inf的边,并不忽略其他所有边。
      //即只清理掉了必在树中的边。
      //若本来图是(n,m,k),则变成了(k+1,m-k+1,k),主要还是在于点数的减少,变成与k=(r-l+1)线性相关。
      //值得思考/学习的地方:并查集只清理关键点、最后一个for中并没有fa[f2]=f1操作的原因。
    }
    
    inline void reduction(int &tot,int cnt=0){//删除必定不在生成树中的边
      clear(tot);sort(Edge+1,Edge+tot+1);
      for(int i=1;i<=tot;++i){
        int f1=find(Edge[i].x),f2=find(Edge[i].y);
        if(f1==f2){
          if(Edge[i].val==Inf)
            que[++cnt]=Edge[i],pos[Edge[i].pos]=cnt;
          continue;
        }
        fa[f1]=f2;que[++cnt]=Edge[i],pos[Edge[i].pos]=cnt;
      }
      tot=cnt;for(int i=1;i<=tot;++i)Edge[i]=que[i];
      //上面的操作删掉了必定不在生成树中的边。
      //若本来图是(n,m,k),则变成了(n,n+k-1,k)。
      //又因为执行reduction操作前图已经是(k+1,m-k+1,k)的了
      //所以图会变成(k,2k,k),减少了边数,图变得完全与k=(r-l+1)线性相关。
    
      //所以每次做mst边数和(r-l+1)(即k)线性相关,由主定理知复杂度是O(q*log_q*(log_q+α))。
    }
    
    //contraction和reduction中都死死抓住了pos和Edge之间的关系。
    
    inline void solve(int l,int r,int dep,LL tval){
      int tot=Enum[dep],mid=(l+r)>>1;
      if(l==r)Eval[Upd[l].k]=Upd[r].v;
      for(int i=1;i<=tot;++i){
        E[dep][i].val=Eval[E[dep][i].pos];
        Edge[i]=E[dep][i];
        pos[E[dep][i].pos]=i;
      }
      //pos和Edge有很重要的关系。
      //pos[i]指的是读入顺序的第i条边在Edge的下标。
      //而Edge.pos指的是这条边是读入的第几条边。
      //即:pos[Edge[i].pos]=i。
      
      if(l==r){
        clear(tot);sort(Edge+1,Edge+tot+1);
        for(int i=1;i<=tot;++i){
          int f1=find(Edge[i].x),f2=find(Edge[i].y);
          if(f1==f2)continue;fa[f2]=f1;tval+=Edge[i].val;
        }
        Ans[l]=tval;return;
      }
      //递归边界。这个时候的图,也就一两个点,一两条边了吧?
      
      for(int i=l;i<=r;++i)Edge[pos[Upd[i].k]].val=-Inf;
      contraction(tot,tval);
      for(int i=l;i<=r;++i)Edge[pos[Upd[i].k]].val=Inf;
      reduction(tot);Enum[dep+1]=tot;
      
      //论文里面的R-C-R的第一个R是没有必要的,只要C-R即可。
      for(int i=1;i<=tot;++i)E[dep+1][i]=Edge[i];
      //这种记录图的方式很巧妙。
      solve(l,mid,dep+1,tval);solve(mid+1,r,dep+1,tval);
      //关键边被修改成Inf就这么传下去了……不过没有任何关系。
    }
    
    int main(){
      n=gi();m=gi();q=gi();
      for(int i=1;i<=m;++i)E[0][i]=(EDGE){gi(),gi(),i,Eval[i]=gi()};
      for(int i=1;i<=q;++i)Upd[i]=(UPD){gi(),gi()};
      Enum[0]=m;solve(1,q,0,0);
      for(int i=1;i<=q;++i)printf("%lld
    ",Ans[i]);
      return 0;
    }
    

      

  • 相关阅读:
    sort()
    type()
    reverse()
    pop()
    remove()
    max()
    len()
    Ext.Js核心函数( 三)
    聚簇索引和非聚簇索引区别
    Ext.js入门(二)
  • 原文地址:https://www.cnblogs.com/fenghaoran/p/8001387.html
Copyright © 2011-2022 走看看