本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。
本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!
题目链接:BZOJ2001
正解:$CDQ$分治+并查集
解题报告:
这道题的$CDQ$分治思想非常巧妙…
考虑我在处理区间$[l,r]$时的情况,我把在这一段区间中会被修改的边称为特殊边,
我先把特殊边的权值设为$-inf$,跑一遍$MST$,此时在$MST$中的非特殊边是必然会在这一段区间中的任何时候被选择的,所以我把他连接的两个点合并起来变成一个点,降低图的规模。
我再把特殊边的权值设为$inf$,跑一遍$MST$,那么此时没在图中的边在这整个区间中一定也不在$MST$中,所以可以去掉。
然后递归做$[l,mid]$、$[mid+1,r]$就好了。
具体实现有点麻烦、复杂,可以看看代码。
注意清空并查集的时候设置上限,保证复杂度。另外,每次只需要处理$[l,r]$范围内的修改!
//It is made by ljh2000 //有志者,事竟成,破釜沉舟,百二秦关终属楚;苦心人,天不负,卧薪尝胆,三千越甲可吞吴。 #include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <algorithm> #include <ctime> #include <vector> #include <queue> #include <map> #include <set> #include <string> #include <complex> #include <bitset> using namespace std; typedef long long LL; typedef long double LB; typedef complex<double> C; const double pi = acos(-1); const int MAXN = 50011; const int MAXM = 50011; const int inf = (1<<30)-1; int n,m,Q,val[MAXM],id[MAXM],gn[17],gm[17],bel[MAXN]; LL ans[MAXN]; bool use[MAXM]; struct edge{ int x,y,z,id; }e[17][MAXM],tmp[MAXM],wyh[MAXM];//每层的边单独存 inline bool cmp(edge q,edge qq){ return q.z<qq.z; } struct ask{ int id,z; }q[MAXN]; inline int getint(){ int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar(); if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w; } namespace Union_set{ int father[MAXN],size[MAXN]; inline void init(int x){ for(int i=1;i<=x;i++) father[i]=i,size[i]=1; } inline int find(int x){ if(father[x]!=x) father[x]=find(father[x]); return father[x]; } inline bool merge(int x,int y){ x=find(x); y=find(y); if(x==y) return 0; if(size[x]>size[y]) swap(x,y); father[x]=y; size[y]+=size[x]; return 1; } } using namespace Union_set; inline void rebuild(int &N,int &M,LL &cost){//把MST中的必然存在的边,所连接的点合并 init(N); int nn=0,nm=0; sort(tmp+1,tmp+M+1,cmp); for(int i=1;i<=M;i++) use[i]=0;//记录是否被合并 for(int i=1;i<=M;i++) { if(merge(tmp[i].x,tmp[i].y) && tmp[i].z!=(-inf) ) use[i]=1,cost+=tmp[i].z; else wyh[++nm]=tmp[i]; } init(N); for(int i=1;i<=M;i++) if(use[i]) merge(tmp[i].x,tmp[i].y); for(int i=1;i<=N;i++) if(find(i)==i) bel[i]=++nn; for(int i=1;i<=N;i++) bel[i]=bel[find(i)]; for(int i=1;i<=nm;i++) { tmp[i]=wyh[i]; id[tmp[i].id]=i; tmp[i].x=bel[tmp[i].x]; tmp[i].y=bel[tmp[i].y]; } N=nn; M=nm; } inline void rebuild2(int &N,int &M){//把MST中不可能出现的边去掉 init(N); int nm=0; sort(tmp+1,tmp+M+1,cmp); for(int i=1;i<=M;i++) if(merge(tmp[i].x,tmp[i].y) || tmp[i].z==inf) tmp[++nm]=tmp[i],id[ tmp[i].id ]=nm; M=nm; } inline void CDQ(int l,int r,int ceng,LL cost){ int N=gn[ceng],M=gm[ceng]; if(l==r) val[q[l].id]=q[l].z; for(int i=1;i<=M;i++) { e[ceng][i].z=val[ e[ceng][i].id ]; tmp[i]=e[ceng][i]; id[tmp[i].id]=i;//记录每条边的出现编号 } init(N); if(l==r) {//直接做一遍最小生成树 sort(e[ceng]+1,e[ceng]+M+1,cmp); for(int i=1;i<=M;i++) if(merge(e[ceng][i].x,e[ceng][i].y)) cost+=e[ceng][i].z; ans[l]=cost; return ; } int mid=(l+r)>>1; //只考虑[l,r]范围内的!!! for(int i=l;i<=r;i++) tmp[ id[ q[i].id ] ].z=-inf; rebuild(N,M,cost); for(int i=l;i<=r;i++) tmp[ id[ q[i].id ] ].z=inf; rebuild2(N,M); gn[ceng+1]=N; gm[ceng+1]=M; for(int i=1;i<=M;i++) e[ceng+1][i]=tmp[i]; CDQ(l,mid,ceng+1,cost); CDQ(mid+1,r,ceng+1,cost); } inline void work(){ n=getint(); m=getint(); Q=getint(); for(int i=1;i<=m;i++) { e[0][i].x=getint(); e[0][i].y=getint(); e[0][i].z=getint(); e[0][i].id=i; val[i]=e[0][i].z; } for(int i=1;i<=Q;i++) q[i].id=getint(),q[i].z=getint(); gn[0]=n; gm[0]=m; CDQ(1,Q,0,0); for(int i=1;i<=Q;i++) printf("%lld ",ans[i]); } int main() { work(); return 0; } //有志者,事竟成,破釜沉舟,百二秦关终属楚;苦心人,天不负,卧薪尝胆,三千越甲可吞吴。