本文版权归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\n",ans[i]);
}
int main()
{
work();
return 0;
}
//有志者,事竟成,破釜沉舟,百二秦关终属楚;苦心人,天不负,卧薪尝胆,三千越甲可吞吴。