1. 程式人生 > 實用技巧 >城市建設

城市建設

本文涉及:cdq分治、MST

一道十分精妙的cdq分治題(o゜▽゜)o。據說線段樹分治+LCQ維護MST也是一種解法,但我並不會...

1 題意

給定一個\(n\)個點,\(m\)條邊的無向帶邊權的圖,和\(q\)次詢問;每一次詢問會修改一條邊的邊權;在每一次詢問後求出當前圖的最小生成樹的權值。

資料範圍:\(n\leq2000,m,q\leq 5000\)

2 解法

這道題的暴力十分顯然,固不再贅述。

設函式\(solve(l,r)\),表示處理修改序列\([l,r]\)中的修改;動態邊表示與修改序\([l,r]\)中的修改有關的邊;反之,靜態邊表示與這段修改無關的邊。

對於動態邊我們暫且不加以考慮,因為隨著函式的遞迴執行,動態邊將不斷地變化為靜態邊;反觀靜態邊,它們從定為靜態邊起身份就不再發生改變。因為我們接下來將著重討論如何通過必選邊和無用邊的劃分,簡化靜態邊邊集。

必選邊:將動態邊的邊權設為\(-\infty\),求一次MST,此時MST中除邊權為\(-\infty\)外的邊都是必選邊

將一條邊設為\(-\infty\),在MST中就會優先考慮這條邊。動態邊都被設為了\(-\infty\),但仍然有靜態邊被選中,說明動態邊集不能保證圖的連通,只有依賴這些靜態邊,圖才能是連通的。根據MST的選取規則,選中的這些靜態邊又是所有可行方案中最優的,因此是必選邊

對於必選邊,將邊的兩個端點縮在一起後,累加邊權。

無用邊:將動態邊的邊權設為\(\infty\),求一次MST,此時沒有出現在MST中的靜態邊是無用邊

相比必選邊,此時動態邊的地位一落千丈。此時沒有被選中的靜態邊在動態邊邊權恢復正常後更加不可能被選上,因此是無用邊。

對於無用邊嘛,丟了就好╰( ̄ω ̄o)

對於某一函式\([l,r]\),其遞迴函式\([l,mid]\)\([mid+1,r]\)的靜態邊集必包含了它自身的靜態邊集,因此不會因為簡化當前邊集而出錯。

每層分治時,用上述兩種方法縮小圖的規模;在最後一層使修改生效,求一遍MST和累加值求和即是答案。此時圖的規模已經不大,可以放心kruskal。

因為分治的順序,在處理\([l,r]\)時,區間\([1,l-1]\)中的修改已被落實,如下圖:

這一點是我當時的理解難點

3 程式碼

#include <bits/stdc++.h>
using namespace std;
#define lor(a,b,c) for(register int a=b;a<=c;++a)
#define ror(a,b,c) for(register int a=c;a>=b;--a)
typedef long long ll;

const int MAXN=2e4+5,MAXM=5e4+5,MAXD=17;
const ll INF=5e10+5;

int n,m,q; int qid[MAXM]; ll qval[MAXM];
ll ans[MAXM]; bool ban[MAXM];
struct data{
	int x,y,id; ll z,bac;
	inline bool operator < (data b) const{
		return z<b.z;
	}
}info[MAXM],work[MAXD][MAXM],up[MAXM];
inline bool cmpf(data a,data b) {return ban[a.id]==ban[b.id]?a.z<b.z:ban[a.id]>ban[b.id];}
inline bool cmpb(data a,data b) {return ban[a.id]==ban[b.id]?a.z<b.z:ban[a.id]<ban[b.id];}
int fat[MAXN]; bool vis[MAXM];
inline void clear() {lor(i,1,n) fat[i]=i;}
inline int find(int a) {while(a^fat[a]) a=fat[a]=fat[fat[a]]; return a;}
inline bool unionn(int a,int b) {int aa=find(a),bb=find(b); return aa==bb?(false):(fat[aa]=bb,true);}

void cdq(int,int,int,int,ll);
inline ll vital(int,int,int,int&);
inline void radish(int,int,int,int&);

int main(){
	#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	#endif

	scanf("%d%d%d",&n,&m,&q);
	lor(i,1,m) scanf("%d%d%lld",&info[i].x,&info[i].y,&info[i].z),info[i].id=i;
	lor(i,1,q) scanf("%d%lld",&qid[i],&qval[i]);

	lor(i,1,m) work[0][i]=info[i];
	cdq(1,1,q,m,0);
	lor(i,1,q) printf("%lld\n",ans[i]);

	return 0;
}

void cdq(int dep,int l,int r,int s,ll v){
	if(l==r) {info[qid[l]].z=qval[l];}
	lor(i,1,s) {work[dep][i]=work[dep-1][i]; work[dep][i].z=info[work[dep][i].id].z;}
	if(l==r){
		ans[l]=v; clear();
		sort(work[dep]+1,work[dep]+1+s);
		lor(i,1,s) if(unionn(work[dep][i].x,work[dep][i].y)) ans[l]+=work[dep][i].z;
		return;
	}
	v+=vital(dep,l,r,s);
	radish(dep,l,r,s);
	int mid=(l+r)>>1; cdq(dep+1,l,mid,s,v); cdq(dep+1,mid+1,r,s,v);
}

inline ll vital(int dep,int l,int r,int &s){
	lor(i,l,r) ban[qid[i]]=true;
	ll ans=0; int tmp=0; lor(i,1,s) vis[i]=false;
	sort(work[dep]+1,work[dep]+1+s,cmpf);
	lor(i,1,s){
		if(unionn(work[dep][i].x,work[dep][i].y)) vis[i]=true;
	}
	lor(i,1,s){
		fat[work[dep][i].x]=work[dep][i].x; fat[work[dep][i].y]=work[dep][i].y;
	}
	lor(i,1,s) if(!ban[work[dep][i].id]&&vis[i]){
		unionn(work[dep][i].x,work[dep][i].y); ans+=work[dep][i].z;
	}
	lor(i,1,s){
		if(ban[work[dep][i].id]||!vis[i]){
			work[dep][i].x=find(work[dep][i].x); work[dep][i].y=find(work[dep][i].y);
			up[++tmp]=work[dep][i];		
		}
	}
	lor(i,1,s) if(!ban[work[dep][i].id]&&vis[i]){
		fat[work[dep][i].x]=work[dep][i].x; fat[work[dep][i].y]=work[dep][i].y;		
	}
	s=tmp; lor(i,1,s) work[dep][i]=up[i];
	lor(i,l,r) ban[qid[i]]=false;
	return ans;
}

inline void radish(int dep,int l,int r,int &s){
	lor(i,l,r) ban[qid[i]]=true;
	int tmp=0; lor(i,1,s) vis[i]=false;
	sort(work[dep]+1,work[dep]+1+s,cmpb);
	lor(i,1,s){
		if(unionn(work[dep][i].x,work[dep][i].y)) vis[i]=true;
	}
	lor(i,1,s){
		fat[work[dep][i].x]=work[dep][i].x; fat[work[dep][i].y]=work[dep][i].y;
	}
	lor(i,1,s){
		if(ban[work[dep][i].id]||vis[i]){
			up[++tmp]=work[dep][i];						
		}
	}
	s=tmp; lor(i,1,s) work[dep][i]=up[i];
	lor(i,l,r) ban[qid[i]]=false;
}