【题解】UVA10099 The Tourist Guide 【Kruskal 重构树】

The Tourist Guide

题目

Link

题解

我们注意到,整个题目的本质就是找到一条从 \(s\)\(t\) 的路径,使得最小权值最大。

这是一条经典的最小瓶颈路问题。可以用 Kruskal 重构树或 Floyd 来解决。

因为 Floyd 是 \(\Theta(n^3)\) 的,虽然在这题可以通过,但是如果 \(1 \le n \le 2 \times 10^5\) 的话,还是有必要掌握有关 Kruskal 重构树的知识的。

首先我们建立原图的 Kruskal 重构树,对于 Kruskal 重构树,有如下性质:

  1. Kruskal 重构树是一个二叉树。
  2. 按原图的最小生成树建立,则为一个大根堆,否则为一个小根堆。
  3. 按最大生成树建立,则任意两点的 LCA 点权即为两点之间路径最小值的最大值。

建法也很简单,跑普通的 Kruskal 最大生成树时以两点间的边权作为其父亲的点权,并查集中两点的父亲连到新的节点即可。

于是我们建完 Kruskal 重构树后,树剖单次 \(\Theta(\log n)\) 查询 LCA 即可。

\(q\) 次询问下的时间复杂度:\(\Theta(m \log m + q \log n)\),本题中因单次查询 \(q=1\)

Code

#include<bits/stdc++.h>
#define DEBUG(x) puts(#x)

inline int read() {
	int x(0),f(0);
	char ch=getchar();
	for(; !isdigit(ch); ch=getchar()) f|=(ch=='-');
	for(;  isdigit(ch); ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
	return f?-x:x;
}

const int N=5e5+5;

struct graph {
	int u,v,w;
	bool operator <(const graph rhs) const {
		return w > rhs.w;
	}
} edge[N];
int n,m,T;

void so1ve();

int main() {
	while(scanf("%d%d",&n,&m)==2 && n!=0 && m!=0)
		so1ve();
	return 0;
}

class dsu {
	private:
		int fa[N];
	public:
		void mst(int M) {
			std::iota(fa+1,fa+M+1,1);
		}
		int find(int x) {
			if(x==fa[x]) return x;
			return fa[x]=find(fa[x]);
		}
		void merge(int x,int y) {
			int fx=find(x),fy=find(y);
			if(fx==fy) return;
			fa[fx]=fy;
			return;
		}
		bool same(int x,int y) {
			return find(x)==find(y);
		}
		void replace(int x,int val) {
			fa[find(x)]=val;
			return;
		}
} _dsu;

struct linked_star {
	int to,nxt;
} G[N<<1];
int head[N],cnt=0;
void addEdge(int u,int v) {
	G[++cnt]= {v,head[u]};
	head[u]=cnt;
}

std::array<int,N> fa,son,dep,siz,top,val,vis;

void dfs1(int x) {
	dep[x]=dep[fa[x]]+1;
	siz[x]=1;
	vis[x]=1;
	for(int i=head[x]; i; i=G[i].nxt) {
		int t=G[i].to;
		if(t==fa[x]) continue;
		fa[t]=x;
		dfs1(t);
		siz[x]+=siz[t];
		if(!son[x] || siz[t]>siz[son[x]])
			son[x]=t;
	}
}

void dfs2(int x,int t) {
	top[x]=t;
	if(son[x]) dfs2(son[x],t);
	for(int i=head[x]; i; i=G[i].nxt) {
		int to=G[i].to;
		if(to==fa[x] || to==son[x]) continue;
		dfs2(to,to);
	}
}

int getlca(int x,int y) {
	while(top[x]!=top[y]) {
		if(dep[top[x]]>=dep[top[y]]) x=fa[top[x]];
		else y=fa[top[y]];
	}
	return (dep[x]>dep[y]?val[y]:val[x]);
}

void ex_kruskal() {
	std::sort(edge+1,edge+m+1);
	int id=n,lim=n<<1;
	_dsu.mst(lim+5);
	for(int i=1; i<=m; i++) {
		int u=_dsu.find(edge[i].u),v=_dsu.find(edge[i].v),w=edge[i].w;
		if(u==v) continue;
		++id;
		_dsu.replace(u,id);
		_dsu.replace(v,id);
		val[id]=w;
		addEdge(u,id), addEdge(id,u);
		addEdge(v,id), addEdge(id,v);
		if(id==lim-1) break;
	}
	for(int i=1; i<=id; i++) if(!vis[i]) dfs1(_dsu.find(i)),dfs2(_dsu.find(i),_dsu.find(i));
}

int testcase;

void so1ve() {
	for(int i=1; i<=m; i++) {
		int u,v,w;
		u=read(), v=read(), w=read();
		edge[i]= {u,v,w};
	}
	ex_kruskal();
	int s,t,sum,ans;
	s=read(), t=read(), sum=read();
	int p=getlca(s,t)-1;
	if(sum%p==0) ans=sum/p;
	else ans=sum/p+1;
	printf("Scenario #%d\nMinimum Number of Trips = %d\n\n", ++testcase,ans);
	for(int i=1; i<=m; i++) edge[i]= {0,0,0};
	for(int i=1; i<=cnt; i++) G[i]= {0,0};
	for(int i=1; i<=n<<1; i++) head[i]=val[i]=fa[i]=son[i]=top[i]=siz[i]=dep[i]=vis[i]=0;
	cnt=0;
}

注意换行和多测清空

注意到,本题还可以二分一个边权的最大值,跑 Dijkstra 看能否到达。

复杂度是 \(\Theta\left(\left(n+m\right)\log^2 n\right)\) 的,这里不展示代码了。

posted @ 2023-01-10 20:40  TheSky233  阅读(21)  评论(0)    收藏  举报