最小生成树(MST)相关三题

[SCOI2005]繁忙的都市

Link
这是一道模板题,不细讲。但是这涉及到最小生成树一个重要性质:
u u u v v v的路径中,最大值最小的一条是最小生成树上的一条路径。

#include<bits/stdc++.h>
#define ll long long
#define N 305
#define inf 0x3f3f3f3f
#define pb push_back
#define mp make_pair
#define lowbit(i) ((i)&(-i))
using namespace std;
int n,m,fa[N],cnt,ans; 
struct node{
	int u,v,w;
	bool operator <(const node &o) const{
		return o.w > w;
	}
}e[N*N];
int find(int x){
	return fa[x]==x?fa[x]:fa[x] = find(fa[x]);
}
void uni(int x,int y){
	fa[find(x)] = find(y);
}
int main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= m;++i)
		scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
	for(int i = 1;i <= n;++i) fa[i] = i;
	sort(e+1,e+m+1);
	for(int i = 1;i <= m;++i){
		if(find(e[i].u) == find(e[i].v)) continue;
		uni(e[i].u,e[i].v);
		cnt++;
		ans = max(e[i].w,ans);
	}
	printf("%d %d",cnt,ans);
	return 0;
}

[HNOI2006]公路修建问题

Link

题目大意

给出一张无向带权图,每条边可以选择建一级道路或者二级道路,花费为 c 1 c_1 c1 c 2 c_2 c2
现在至少要建 k k k条一级道路,问生成树中边权最大值最小是多少。

做法

二分最大值,跑 k r u s k a l kruskal kruskal的时候,先把能选的都选成一级道路,剩下的选二级道路,判断是否满足条件。

#include<bits/stdc++.h>
#define ll long long
#define N 10015
#define M 20015
#define inf 0x3f3f3f3f
#define pb push_back
#define mp make_pair
#define lowbit(i) ((i)&(-i))
using namespace std;
int n,k,m,fa[N];
struct node{
	int u,v,c1,c2;
}e[M]; 
int find(int x){
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
void uni(int x,int y){
	fa[find(x)] = find(y);
}
bool check(int val){
	for(int i = 1;i <= n;++i) fa[i] = i;
	int cnt = 0;
	for(int i = 1;i <= m;++i){
		if(find(e[i].u)==find(e[i].v)||e[i].c1 > val) continue;
		uni(e[i].u,e[i].v);
		cnt++;
	}
	if(cnt < k) return 0;
	for(int i = 1;i <= m;++i){
		if(find(e[i].u)==find(e[i].v)||e[i].c2 > val) continue;
		uni(e[i].u,e[i].v);
		cnt++;
	}
	if(cnt != n-1) return 0;
	return 1;
}
int main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>k>>m;
	int l = 0,r = 0;
	for(int i = 1;i < m;++i){
		cin>>e[i].u>>e[i].v>>e[i].c1>>e[i].c2;
		r = max(max(e[i].c1,e[i].c2),r);
	}
	while(l+3<r){
		//cout << l << ' ' << r << endl;
		int mid = (l+r)>>1;
		if(check(mid)) r = mid;
		else l = mid;
	}
	for(int i = l;i <= r;++i){
		if(check(i)){
			cout<<i;
			break;
		}
	}
	return 0;
}

BZOJ3732 Network

Link

题意

给出一张无向图,有 q q q次询问,每次问你 u u u v v v所有路径中,边权最大值最小的一条的最大值是多少。
不难想到求出其最小生成树,对于每一个询问,在树上倍增求出 l c a lca lca的同时求出最小值,复杂度 O ( q l o g n ) O(qlogn) O(qlogn)
然后我发现了这道题

Loj #137. 最小瓶颈路 加强版
n ≤ 7 ∗ 1 0 4 n \le 7*10^4 n7104, q ≤ 1 0 7 q \le 10^7 q107
刚刚所讲的做法卡卡常数就能莽过去 不能通过此题。
于是我们有了一个新的解法,可以在 O ( n l o g n + q ) O(nlogn+q) O(nlogn+q)的复杂度下通过。

做法

1. k r u s k a l kruskal kruskal重构树

对原图跑一遍 k r u s k a l kruskal kruskal,若 u , v u,v u,v不在同一集合内,新建一个点 t o p top top,合并 u , v , t o p u,v,top u,v,top所在集合,并将 u , v u,v u,v的祖先作为 t o p top top的儿子, t o p top top有点权 w ( u , v ) w(u,v) w(u,v) u , v u,v u,v的边权。然后我们就得到了一个重要的性质, l c a ( u , v ) lca(u,v) lca(u,v)的点权就是 u u u v v v所有路径中,边权最大值最小的一条的最大值。

2. l c a lca lca

如何快速求出两点之间的 l c a lca lca?
倍增: 预处理 O ( n l o g n ) O(nlogn) O(nlogn) 查询 O ( l o g n ) O(logn) O(logn) 显然不行。
于是我们有一种 预处理 O ( n l o g n ) O(nlogn) O(nlogn) 查询 O ( 1 ) O(1) O(1)的做法
求出一棵树的欧拉序,然后做静态 R M Q RMQ RMQ
欧拉序类似于 d f s dfs dfs序,只不过欧拉序是进入一次加 1 1 1,回溯也加 1 1 1,然后我们记录下 f i r [ i ] fir[i] fir[i]表示第一次访问 i i i的欧拉序,我们发现 l c a ( u , v ) lca(u,v) lca(u,v)就是 f i r [ u ] fir[u] fir[u] f i r [ v ] fir[v] fir[v]之间深度最小的节点, R M Q RMQ RMQ维护即可。
关于RMQ
关于lca

#include<bits/stdc++.h>
#define ll long long
#define N 1000015
#define inf 0x3f3f3f3f
#define pb push_back
#define mp make_pair
#define lowbit(i) ((i)&(-i))
using namespace std;
int n,m,q,fa[N],fir[N],dep[N],id[30][N],val[N],lg2[N],tot;
int A,B,C,P;
const int mod = 1e9+7;
inline int rnd(){ return A=(A*B+C)%P;}
struct node{
	int u,v,w;
	bool operator <(const node &a) const{
		return a.w > w;
	}
}G[N];
vector<int> e[N];
int find(int x){
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
int Kruskal(){
	int top = n;
	for(int i = 1;i <= 2*n;++i) fa[i] = i;
	for(int i = 1;i <= m;++i){
		int x =  find(G[i].u),y = find(G[i].v);
		if(x == y) continue;
		//cout << x << " TTT " << y << " " << top+1 <<endl; 
		val[++top] = G[i].w;
		fa[x] = fa[y] = top;
		e[top].pb(x);e[top].pb(y);
		e[x].pb(top);e[y].pb(top);
	}
	return top;
}
void dfs(int u,int f){
	//cout <<"EEE   " << u << endl;
	fir[u] = ++tot;
	dep[u] = dep[f]+1;
	id[0][tot] = u;
	for(int i = 0;i < e[u].size();++i){
		int v = e[u][i];
		if(v == f) continue;
		dfs(v,u);
		id[0][++tot] = u;
	}
}
void RMQ(){
	for(int i = 2;i <= tot;++i) lg2[i] = lg2[i>>1]+1;
	for(int j = 1;j <= 20;++j){
		for(int i = 1;i+(1<<j)-1 <= tot;++i){
			if(dep[id[j-1][i]] < dep[id[j-1][i+(1<<(j-1))]])
				id[j][i] = id[j-1][i];
			else id[j][i] = id[j-1][i+(1<<(j-1))];
		}
	}
}
int LCA(int u,int v){
	int l = fir[u],r = fir[v];
	if(l > r) swap(l,r);
	int k = lg2[r-l+1];
	if(dep[id[k][l]] < dep[id[k][r-(1<<k)+1]]) return id[k][l];
	return id[k][r-(1<<k)+1];
}
int main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= m;++i)
		scanf("%d%d%d",&G[i].u,&G[i].v,&G[i].w);
	sort(G+1,G+m+1);
	int root = Kruskal();
//	for(int i = 1;i <= 2*n;++i){
//		cout << "fff " << i << ":  ";
//		for(auto v:e[i]){
//			cout << v << ' ';
//		}
//		cout << endl;
//	}
	dfs(root,0);
	RMQ();
	scanf("%d",&q);
	scanf("%d%d%d%d",&A,&B,&C,&P);
	int ans = 0;
	while(q--){
		int u,v;
		u = rnd()%n+1,v=rnd()%n+1;
		//cout << LCA(u,v) << endl;
		ans = (ans%mod+val[LCA(u,v)]%mod)%mod; 
	}
	printf("%d",ans);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值