題目大意:動態最小生成樹,能夠離線,每次修改後回答,點數20000,邊和修改都是50000。ios
顧昱洲是真的神:顧昱洲_淺談一類分治算法算法
連接: https://pan.baidu.com/s/1c2lkayO 密碼: 83rx學習
講的很妙,大體的幾個注意點在代碼裏面也有提到。spa
#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; }