洛谷 P2245 星际导航 kruskal重构树

题目描述
sideman 做好了回到Gliese 星球的硬件准备,但是 sideman 的导航系统还没有完全设计好。为了方便起见,我们可以认为宇宙是一张有 N 个顶点和 M 条边的带权无向图,顶点表示各个星系,两个星系之间有边就表示两个星系之间可以直航,而边权则是航行的危险程度。

sideman 现在想把危险程度降到最小,具体地来说,就是对于若干个询问 (A, B)sideman 想知道从顶点 A 航行到顶点 B 所经过的最危险的边的危险程度值最小可能是多少。作为 sideman 的同学,你们要帮助sideman 返回家园,兼享受安全美妙的宇宙航行。所以这个任务就交给你了。

输入格式
第一行包含两个正整数 N 和 M,表示点数和边数。

之后 M 行,每行三个整数 A,B 和 L,表示顶点 A 和 B 之间有一条边长为 L 的边。顶点从 1 开始标号。

下面一行包含一个正整数 Q,表示询问的数目。

之后 Q 行,每行两个整数 A 和 B,表示询问 A 和 B 之间最危险的边危险程度的可能最小值。

输出格式
对于每个询问, 在单独的一行内输出结果。如果两个顶点之间不可达, 输出 impossible。

输入输出样例
输入 #1
4 5
1 2 5
1 3 2
2 3 11
2 4 6
3 4 4
3
2 3
1 4
1 2
输出 #1
5
4
5
说明/提示
对于 40% 的数据,满足N≤1000,M≤3000,Q≤1000。

对于 80% 的数据,满足 N≤10000,M≤10^5,Q≤1000。

对于 100% 的数据,满足N≤10 ^ 5,M≤3×10 ^ 5 ,Q≤10 ^ 5 ,L≤10 ^ 9 。数据不保证没有重边和自环。

解法:kruskal重构树+树剖求LCA

前言:第一次听说这东西,感觉很神奇,其实也不算很难,下面引用这个大佬在洛谷上关于kruskal重构树的解释

重构树的过程

  1. 将所有边按边权从小到大排序

  2. 每次最小的一条边,如果条边相连的两个点在同一个集合中,那么就跳过,否则就将这两个点的祖先都连到一个虚点上去,让这个虚点的点权等于这条边的边权

重构树的性质

  • 原本最小生成树上的点在重构树里都是叶节点

  • 从任何一个点往根上引一条路径,这条路径经过的点的点权单调不降(最大生成树单调不升)

  • 任意两点之间路径的最大边权就是他们的LCA的点权

可以解决的问题

  • 最小生成树上的最大边权
  • 最大生成树上的最小边权

然后就没什么了,直接放代码了

AC代码

#include<cstdio>
#include<algorithm>
#define si 300005
#define re register int
using namespace std;
struct edge {
	int nex,to,w;
}e[si<<1],a[si<<1];
int n,m,k,num,q,top[si],head[si],key[si];
int f[si],d[si],fa[si],son[si],sum[si];
inline int read() {
	int x=0,cf=1;
	char ch=getchar();
	while(ch<'0'||ch>'9') {
		if(ch=='-') cf=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return x*cf;
}
inline bool cmp(edge A,edge B) { return A.w<B.w; }
inline void dfs1(int x) {
	sum[x]=1; int maxx=-1;
	for(re i=head[x];i;i=e[i].nex) {
		int y=e[i].to;
		if(d[y]) continue;
		d[y]=d[x]+1,f[y]=x; dfs1(y);
		sum[x]+=sum[y];
		if(sum[y]>maxx) maxx=sum[y],son[x]=y;
	}
}

inline void dfs2(int x,int topf) {
	top[x]=topf;
	if(!son[x]) return; dfs2(son[x],topf);
	for(re i=head[x];i;i=e[i].nex) {
		int y=e[i].to;
		if(y==f[x]||y==son[x]) continue;
		dfs2(y,y);
	}
}
inline int lca(int x,int y) {
	while(top[x]!=top[y]) {
		if(d[top[x]]<d[top[y]]) swap(x,y);
		x=f[top[x]];
	}
	if(d[x]<d[y]) return x; return y;
}
inline int find(int x) {
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
inline void add(int x,int y) {
	e[++num].to=y,e[num].nex=head[x],head[x]=num;
}
int main() {
	n=read(),m=read();
	for(re i=1;i<=(n<<1);i++) fa[i]=i;
	for(re i=1;i<=m;i++) {
		a[i].nex=read();
		a[i].to=read();
		a[i].w=read();
	}
	sort(a+1,a+m+1,cmp); k=n;
	for(re i=1;i<=m;i++) {
		int xx=find(a[i].nex),yy=find(a[i].to);
		if(xx==yy) continue;
		fa[xx]=fa[yy]=++k;
		add(k,xx),add(xx,k);
		add(k,yy),add(yy,k);
		key[k]=a[i].w;
	}
	for(re i=k;i;i--) {
		if(!d[i]) {
			d[i]=1,dfs1(i),dfs2(i,i);
		}
	}
	q=read(); while(q--) {
		int x=read(),y=read();
		if(find(x)!=find(y)) printf("impossible\n");
		else printf("%d\n",key[lca(x,y)]);
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值