首页 > 代码库 > BZOJ2001 [Hnoi2010]City 城市建设

BZOJ2001 [Hnoi2010]City 城市建设

本文版权归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;
}
//有志者,事竟成,破釜沉舟,百二秦关终属楚;苦心人,天不负,卧薪尝胆,三千越甲可吞吴。

  

BZOJ2001 [Hnoi2010]City 城市建设