1. 程式人生 > >BZOJ2001 HNOI2010 城市建設

BZOJ2001 HNOI2010 城市建設

遞歸 密碼 truct 註意 red inf str define target

  題目大意:動態最小生成樹,可以離線,每次修改後回答,點數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\n",Ans[i]);
  return 0;
}

  

BZOJ2001 HNOI2010 城市建設