[NOIP2013 提高组] 货车运输(C++,Kruskal重构树)

题目描述

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

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

输入格式

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

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

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

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

输出格式

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

样例 #1

样例输入 #1

4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3

样例输出 #1

3
-1
3

提示

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

对于 60 % 60\% 60% 的数据, 1 ≤ n < 1000 1 \le n < 1000 1n<1000 1 ≤ m < 5 × 1 0 4 1 \le m < 5\times 10^4 1m<5×104 1 ≤ q < 1000 1 \le q< 1000 1q<1000

对于 100 % 100\% 100% 的数据, 1 ≤ n < 1 0 4 1 \le n < 10^4 1n<104 1 ≤ m < 5 × 1 0 4 1 \le m < 5\times 10^4 1m<5×104
1 ≤ q < 3 × 1 0 4 1 \le q< 3\times 10^4 1q<3×104 0 ≤ z ≤ 1 0 5 0 \le z \le 10^5 0z105

解题思路:

不知道Kruskal重构树是什么的建议先去看一下Kruskal重构树

接下来不讲解该算法,只利用其性质

题意很好理解,大概就是求一条路径,使得路径上的最小边权最大

这题意第一眼看上去好像能二分,然后转头去看询问次数max_q = 3e4,每次都二分必然TLE

那么再回到题意分析上

题中不关心路径的长度,只关心路径上最小边权值

那么满足最低限度的两点相连即可,其他边均可忽略

所以我们可以生成一棵最大生成树,这里采用Kruskal生成最大生成树也可以,但不予说明,我们只讲解Kruskal重构树

为什么要用Kruskal重构树算法呢?

回想Kruskal重构树生成的最小生成树有一个特点:

原树中两点之间路径上边权的最大值等于新树上两点的最近公共子节点的点权

那么Kruskal重构树生成的最大生成树就有这样一个特点:

原树中两点之间路径上边权的最小值等于新树上两点的最近公共子节点的点权

原因是在生成树的过程中,我们按边权从大到小进行尝试,边权较大的自然就在更深的地方,边权较小的自然就在更浅的地方

所以思路清晰了:Kruskal重构树 + LCA = AC

AC代码如下

#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int max_n = 2e4;
const int max_m = 5e4;
const int max_q = 3e4;
const int max_p = 1e5;
const int max_expo = 16;

struct edge { int u, v, p; };
class less_queue {
public:
	bool operator()(edge e_1, edge e_2) {
		return e_1.p < e_2.p;
	}
};

//LCA
int dp[max_n + 1][max_expo];//倍增寻访LCA
int depth[max_n + 1];//深度
int lg[max_n + 1];//(log_2 i) + 1
int value[max_n + 1];
//Kruskal
priority_queue<edge, vector<edge>, less_queue>p_q;//优先队列存边
int fa[max_n + 1], fa2[max_n + 1];//fa为Kruskal重构树,fa2为路径压缩树
int son[max_n + 1][2];//左右子树

//寻访根节点
int find(int x) {
	return fa2[x] == x ? x : (fa2[x] = find(fa2[x]));
}

bool is_in_same(int x, int y) {
	if (find(x) == find(y)) return true;
	else return false;
}

//存图
void add_edge(int u, int v, int p) {
	p_q.push(edge{ u,v,p });
}

int lca(int u, int v) {
    //两点不相连
	if (!is_in_same(u, v)) return -1;
	//深度同步
	if (depth[u] < depth[v]) swap(u, v);//保证depth[u] >= depth[v]
	while (depth[u] != depth[v])//深度同步
		u = dp[u][lg[depth[u] - depth[v]] - 1];
	if (dp[u][0] == dp[v][0]) return value[dp[u][0]];//已找到LCA

	//倍增寻访
	for (int i = lg[depth[u]] - 1; i >= 0; i--) {
		if (dp[u][i] != dp[v][i]) {
			u = dp[u][i];
			v = dp[v][i];
		}
	}
	return value[dp[u][0]];
}

//LCA初始化(动态规划)
void init(int s) {
    //初始化s节点
	for (int i = 1; i <= lg[depth[s]] - 1; i++) {
		dp[s][i] = dp[dp[s][i - 1]][i - 1];
	}
    //继续初始化子节点
	if (son[s][0]) {
		for (int i = 0; i < 2; i++) {
			int v = son[s][i];
			depth[v] = depth[s] + 1;//初始化深度
			dp[v][0] = s;//设置直接父节点
			init(v);
		}
	}
}

int exKruskal(int n) {//传入节点数量
	edge t;
	int u, v;
	while (!p_q.empty()) {//按边权从大到小尝试
		t = p_q.top();
		p_q.pop();//取出队首
		u = t.u; v = t.v;
		u = find(u); v = find(v);
		if (is_in_same(u, v)) continue;//同一棵子树中,continue
		//创建新节点
		n++;
		fa[n] = fa[u] = fa[v] = n;
		fa2[n] = fa2[u] = fa2[v] = n;//连接父节点
		son[n][0] = u; son[n][1] = v;//连接子节点
		value[n] = t.p;//赋权
	}
	return n;//返回新节点数量
}

int main() {
	int n, m, u, v, p, q;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {//初始化
		lg[i] = lg[i - 1] + ((1 << lg[i - 1]) == i);
		fa[i] = fa2[i] = i;
	}
	for (int i = 0; i < m; i++) {//存图
		cin >> u >> v >> p;
		add_edge(u, v, p);
	}
	n = exKruskal(n);//Kruskal重构树
	for (int i = 1; i <= n; i++) {//LCA初始化
		if (find(i) == i) {
			depth[i] = 1;
			dp[i][0] = i;
			init(i);
		}
	}
    //LCA
	cin >> q;
	for (int i = 0; i < q; i++) {
		cin >> u >> v;
		cout << lca(u, v) << endl;
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

WitheredSakura_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值