luogu p1967 货车运输

题意:有n座城市,其间有m条道路相连,构成无向图,图中可能存在孤点。每条道路有一个限重值,货车走这条道路时所载货物的重量不应超过限重值。有q次询问,每次询问给出两个城市,问从城市1到城市2之间货车的最大载重是多少。

思路:从第一个城市 即节点1开始建立一颗最大生成树,然后在树上进行LCA操作,找出最近公共祖先,两个节点之间最小边权的最大值(即为题中的最大载重)。

1.先构造最大生成树,用Kruskal构造。和最小生成树的方法一样,条件改成取较大值就行。

2.预处理yuchuli构造倍增数组。

3.LCA函数,一个简单的查询操作。(转载自)LCA教程

x、y分别是树上的两个点,找他们的最近的公共祖先

开头用一个判断,规定 x 的深度一定比 y 大,否则就用 swap函数 交换一下。

第一个循环是:让 x 往上跳,去找 ‘y的所在层’

如果 x 和 y 重合了,就已经是答案了;

否则 让 x 和 y 一起向上跳,跳到 ‘拥有共同父亲的下一层’

轻轻地输出这位父亲,完成~~~

先上代码。代码里面p[][]数组对应后面记录的dp[][]数组,代码里的dp[][]数组用来存边权的。

/*
 * Do not go gentle into that good night
 *                                    ----Dylan Thomas
 * Author:  looooop
 * Created Time:  2018年12月09日 星期日 16时02分22秒
 * File Name: p1967.cpp
 */
#include <iostream>  
#include <stdio.h>  
#include <string.h>  
#include <stack>  
#include <queue>  
#include <map>  
#include <set>  
#include <vector>  
#include <math.h>  
#include <bitset>  
#include <algorithm>  
#include <climits>  
using namespace std;

#define lson 2*i  
#define rson 2*i+1  
#define LS l,mid,lson  
#define RS mid+1,r,rson  
#define UP(i,x,y) for(i=x;i<=y;i++)  
#define DOWN(i,x,y) for(i=x;i>=y;i--)  
#define MEM(a,x) memset(a,x,sizeof(a))  
#define W(a) while(a)  
#define gcd(a,b) __gcd(a,b)  
#define LL long long  
#define N 1000005  
#define MOD 1000000007  
#define INF 0x3f3f3f3f  
#define EXP 1e-8  
#define lowbit(x) (x&-x)
#define MAX 100006
int n,m;
int x,y,z;
int q,tot;
struct node1{
	int x,y,c;
};
struct node2{
	int x,y,c,g;
};
int vis[MAX],head[MAX],fa[MAX],deep[MAX];
int dp[MAX][26],p[MAX][26];
node1 G[MAX];
node2 tree[MAX];
void init(){
	for(int i = 1; i <= n; i++)
		fa[i] = i;
	MEM(vis,0);MEM(deep,0);
	MEM(head,0);
}
bool cmp(node1 a,node1 b){
	return a.c > b.c;
}
int Find(int x){
	if(x != fa[x]){
		return fa[x] = Find(fa[x]);
	}
	return x;
}
void conn(int x,int y,int value){
	tot++;
	tree[tot].x = x;
	tree[tot].y = y;
	tree[tot].c = value;
	tree[tot].g = head[x];
	head[x] = tot;
}
void Kruskal(){
	//sort(G+1,G+m+1,cmp);
	int cnt = 0;
	for(int i = 1; i <= m; i++){
		int xFa = Find(G[i].x);
		int yFa = Find(G[i].y);
		if(xFa != yFa){
			fa[xFa] = yFa;
			cnt++;
			conn(G[i].x,G[i].y,G[i].c);
			conn(G[i].y,G[i].x,G[i].c);
			if(cnt == n-1)	break;
		}
	}
}
void dfs(int st){
	for(int i = head[st]; i ; i = tree[i].g){
		int v = tree[i].y;
		if(deep[v] == 0){
			deep[v] = deep[st] + 1;
			p[v][0] = st;
			dp[v][0] = tree[i].c;
			dfs(v);
		}
	}
}
void yuchuli(){
	for(int i=1;i<=n;i++){
		if(deep[i] == 0){
			deep[i] = 1;
			p[i][0] = 0;
			dfs(i);
		}
	}
	dfs(1);
	for(int i=1;i<=20; i++){
		for(int j = 1; j <= n; j++){
			p[j][i] = p[p[j][i-1]][i-1];
			dp[j][i] = min(dp[j][i-1],dp[p[j][i-1]][i-1]);
		}
	}
}
int lca(int x,int y){
	int ans = INF;
	if(deep[x] < deep[y])	swap(x,y);
	for(int i=20; i >= 0; i--){
		if(deep[p[x][i]] >= deep[y]){
			ans = min(ans,dp[x][i]);
			x = p[x][i];
		}
	}
	if(x == y)	return ans;
	for(int i = 20; i>=0;i--){
		if(p[x][i]!=p[y][i]){
			ans = min(ans,min(dp[x][i],dp[y][i]));
			x = p[x][i];
			y = p[y][i];
		}
	}
	ans = min(ans,min(dp[x][0],dp[y][0]));
	return ans;
}

int main(int argc,char *argv[]){
	scanf("%d%d",&n,&m);
	for(int i =1; i <= m; i++){
		scanf("%d%d%d",&G[i].x,&G[i].y,&G[i].c);
	}
	//sort(G+1,G+m+1,cmp);
	sort(G+1,G+m+1,cmp);
	//for(int i = 1; i <= m; i++)
	//	printf("%d\n",G[i].c);
	//printf("\n");
	init();
	Kruskal();
	yuchuli();
	//int q;
	scanf("%d",&q);
	for(int i = 1; i <= q; i++){
		int a,b;
		scanf("%d%d",&a,&b);
		if(Find(a) != Find(b))
			//printf("Find(a)=%d Find(b)=%d\n",Find(a),Find(b)),printf("-1\n");
			printf("-1\n");
		else{
			int ANS = lca(a,b);
			printf("%d\n",ANS);
		}
	}
	return 0;
}

再记录一下倍增求LCA的方法,根据上文提到的链接。

LCA是求树上最近公共祖先的方法,主要有Tarjan的深搜离线操作和倍增数组的在线操作。这里记录倍增在线LCA

1.确定树的根,使树的根层数是1,定义dp[r][0]作为边界,往上跳就出界了

deep[r] = 1;  dp[r][0] = 0;

2.构造树,主要把点都分好层,做好深度标记,使用dfs来实现,让凌乱的图变成一棵树deep[y]表示y号点的深度,层数。dp{y][0]表示y点向上跳第一步的时候(也是跳一步,2^0步)必然是自己父亲。

deep[y]  =deep[x] + 1;    dp[y][0] = x;

3.

通过两层循环,把倍增数组构造出来:

第一层循环是步数,从小到大;

第二层循环是点数,有的人习惯把点数写成20,其实根据实际log(n)/log(2)就够用了,因为你想一想,2^20是多少

//=================================

这是本次题解最难最难最难的东东!!

原理类似:合并石子:dp的经典思路,先把小规模的状态都算出来,再层层推出大规模的数据。

//=================================

4.最后一步,LCA函数

x、y分别是树上的两个点,找他们的最近的公共祖先

开头用一个判断,规定 x 的深度一定比 y 大,否则就用 swap函数 交换一下。

第一个循环是:让 x 往上跳,去找 ‘y的所在层’

如果 x 和 y 重合了,就已经是答案了;

否则 让 x 和 y 一起向上跳,跳到 ‘拥有共同父亲的下一层’

轻轻地输出这位父亲,完成~~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值