P1967 [NOIP2013 提高组] 货车运输(倍增+lca)

题目描述

A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。

现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

输入格式

第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道路。

接下来 m 行每行三个整数 x, y, z每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。
注意: x ≠ y,两座城市之间可能有多条道路 。

接下来一行有一个整数 q,表示有 q 辆货车需要运货。

接下来 q 行,每行两个整数 x,y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,保证 x ≠ y。

输出格式

共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。
如果货车不能到达目的地,输出 -1。

输入输出样例

输入
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
输出
3
-1
3

说明/提示

  • 对于 30% 的数据,1≤n<1000,1≤m<10,000,1≤q<1000;

  • 对于 60% 的数据,1≤n<1000,1≤m<5×104,1≤q<1000;

  • 对于 100% 的数据,1≤n<104,1≤m<5×104,1≤q<3×104,0≤z≤105

思路

  • 首先,要使最小值最大,而有好多条边,那么我们很容易想到求出这个图的最大生成树(因为假如走的不是树边,那么载重显然更小,与我们要求的不符)。
  • 重构出最大生成树后,要求的就变成了求树上任意两点间的最小值,我们可以通过求lca来求。

算法流程

  1. 建有向图,用Kruskal求出最大生成树的树边,用这些树边建一个新的无向图。
  2. dfs预处理出fa[x][i](x的2i辈祖宗)和mi[x][i](x到2i辈祖宗经过的最小树边)(注意图可能是不联通的,所以预处理时要对每棵树都dfs一遍。)。
  3. 求出x,y的lca,把x到y的路径拆分成两部分:x到lcay到lca。分别通过倍增求出这两条路径的最小树边,然后再取min,就是最终答案了。

代码

#include<cstdio>
#include<algorithm>
#define ri register int
using namespace std;
const int maxn=1e5+7;
const int inf=0x7fffffff;
struct E1{
	int u,v,w,n;
}e1[maxn];
int head1[maxn];//原图 
struct E{
	int v,w,n;
}e[maxn<<1];
int head[maxn];//新树 
int f[maxn];//代表元素
int d[maxn],lg[maxn];//深度,lg 
int mi[maxn][37];//最小值 
int fa[maxn][37];//祖宗 
int n,m,cnt1,cnt;
inline int read(){//快读 
	int x=0,f=0;char c=getchar();
	while(c>'9'||c<'0') f=c=='-',c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return f?-x:x;
}
inline void add1(int u,int v,int w){//原图 
	e1[cnt1].u=u;
	e1[cnt1].v=v;
	e1[cnt1].w=w;
	e1[cnt1].n=head1[u];
	head1[u]=cnt1;
	cnt1++;
}
inline void add(int u,int v,int w){//新树 
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].n=head[u];
	head[u]=cnt;
}
bool cmp(E1 x,E1 y){//快排的cmp 
	return x.w>y.w;
}
int get(int x){//并查集 
	if(x==f[x]) return x;
	return f[x]=get(f[x]);
}
void Kruskal(){//求最大生成树 
	sort(e1,e1+cnt1,cmp);
	for(ri i=0;i<m;++i)
	{
		int u=e1[i].u,v=e1[i].v,w=e1[i].w;
		if(get(u)==get(v)) continue;
		f[get(u)]=get(v);
		add(u,v,w),add(v,u,w); //新树建双边 
	}
}
void dfs(int u,int fath){//预处理 
	for(ri i=1;i<=lg[d[u]]+1;++i)
	{
		fa[u][i]=fa[fa[u][i-1]][i-1];
		mi[u][i]=min(mi[fa[u][i-1]][i-1],mi[u][i-1]);
	}
	for(ri i=head[u];~i;i=e[i].n)
	{
		int v=e[i].v,w=e[i].w;
		if(e[i].v==fath) continue;
		d[v]=d[u]+1;
		fa[v][0]=u;
		mi[v][0]=w;
		dfs(v,u);
	}
}
int lca(int x,int y){//lca 
	if(d[x]<d[y]) return lca(y,x);
	while(d[x]>d[y]) x=fa[x][lg[d[x]-d[y]]];
	if(x==y) return x;
	for(ri i=lg[d[x]];i>=0;--i)
		if(fa[x][i]!=fa[y][i])
			x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
int find(int x,int l){//求最小值 
	int minx=inf;
	while(d[x]>d[l])
	{
		minx=min(minx,mi[x][lg[d[x]-d[l]]]);
		x=fa[x][lg[d[x]-d[l]]];
	}
	return minx;
}
int main(){
	n=read(),m=read();
	for(ri i=1;i<=n;++i) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
	for(ri i=1;i<=n;++i) lg[i]--,f[i]=i,head[i]=-1;//预处理lg 
	for(ri i=1;i<=m;++i)
	{
		int u=read(),v=read(),w=read();
		add1(u,v,w);//原图只用建单边 
	}
	Kruskal();
	for(ri i=1;i<=n;++i)
		if(f[i]==i) d[i]=1,fa[i][0]=i,mi[i][0]=inf,dfs(i,0);
		//每棵树都要预处理 
	int q=read();
	while(q--)
	{
		int x=read(),y=read();
		if(get(x)!=get(y)) printf("-1\n");
		else
		{
			int l=lca(x,y);
			printf("%d\n",min(find(x,l),find(y,l)));//分两部分 
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Robin_w2321

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值