Vijos 1843 货车运输(最大生成树 + 树上倍增模板)

解法:首先想到肯定是尽量用边权大的边,所以直接用最大生成树建图即可。建完图之后,由于询问数很大,所以要想一个高效的求两点之间边权最小的边的办法。一开始想到树链剖分,感觉很麻烦,代码量巨大。想用LCA的ST表来维护,但是发现维护困难,因为是边而不是点。最后才知道要用树上倍增做。预处理出深度小的点到深度大的i点最小的边权是多少。先求出x和y的lca,然后直接求出x到lca, y到lca的最小边权。取最小值即可。

代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 5e5 + 5;
const int M = 25; //树的深度 
int dp[2 * maxn][M];  //这个数组记得开到2*maxn,因为遍历后序列长度为2*n-1
bool vis[maxn];
struct edge {
    int u, v, w, next;
} e[2 * maxn];
int tot, head[maxn];
inline void add(int u ,int v ,int w ,int &k) {
    e[k].u = u; e[k].v = v; e[k].w = w;
    e[k].next = head[u]; head[u] = k++;
    u = u ^ v; v = u ^ v; u = u ^ v;
    e[k].u = u; e[k].v = v; e[k].w = w;
    e[k].next = head[u]; head[u] = k++;
}
int dir[maxn], d[maxn][M];
int fa[maxn][M];
void dfs(int u ,int dep) {
    vis[u] = true; 
    for(int i = 1; i < M; i++) {
    	if(dir[u] < (1 << i))
    		break;
    	fa[u][i] = fa[fa[u][i - 1]][i - 1];
    	d[u][i] = min(d[u][i - 1], d[fa[u][i - 1]][i - 1]);
	} 
    for(int k = head[u]; ~k; k = e[k].next)
        if( !vis[e[k].v] ) {
            int v = e[k].v , w = e[k].w;
//            cout << v << ' ' << w << '\n';
            dir[v] = dir[u] + 1;
            fa[v][0] = u;
            d[v][0] = w;
            dfs(v, dep + 1);
        }
}

int LCA(int x, int y) {
	if(dir[x] < dir[y])
		swap(x, y);
	int t = dir[x] - dir[y];
	for(int i = 0; i < M; i++)
		if((1 << i) & t)
			x = fa[x][i];
	for(int i = M - 1; i >= 0; i--) {
		if(fa[x][i] != fa[y][i]) {
			x = fa[x][i];
			y = fa[y][i];
		}
	}
	if(x == y) //如果y就是x的最近公共祖先/x本来就等于y 
		return x;
	return fa[x][0];
}

int ask(int x, int y) {
	int ans = 0x3f3f3f3f;
	int t = dir[x] - dir[y];
	for(int i = 0; i < M; i++) {
		if((1 << i) & t) {
			ans = min(ans, d[x][i]);
			x = fa[x][i];
		}
	}
	return ans;
}

int n, m, par[maxn];
struct Node {
	int u, v, val;
	bool operator < (const Node& t) const {
		if(val > t.val)
			return 1;
		return 0;
	}
}edge[maxn];
int Find(int x) {
	int tmp = x;
	while(x != par[x])
		x = par[x];
	int root = x;
	x = tmp;
	while(x != par[x]) {
		tmp = par[x];
		par[x] = root;
		x = tmp;
	}
	return root;
}
void Kruskal(int &num) {
	for(int i = 1; i <= n; i++)
		par[i] = i;
	for(int i = 0; i < m; i++) {
		int x = Find(edge[i].u);
		int y = Find(edge[i].v);
		if(x != y) {
			par[y] = x;
			add(edge[i].u, edge[i].v, edge[i].val, num);
		}
	}
}
int main() {
//#ifndef ONLINE_JUDGE 
//    freopen("in.txt","r",stdin);
//#endif
	memset(head, -1, sizeof(head));
    memset(vis, false, sizeof(vis));
    int num = 0, q, x, y;;
    scanf("%d%d", &n, &m);
    for(int i = 0; i < m; i++)
    	scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].val);
	sort(edge, edge + m);
	Kruskal(num);
    tot = 0; 
	dir[Find(1)] = 0;
    dfs(par[1], 1);
    scanf("%d", &q);
    while(q--) {
        scanf("%d%d", &x, &y);
        if(Find(x) != Find(y)) {
        	printf("-1\n");
        	continue;
		}
		int lca = LCA(x, y);
		printf("%d\n", min(ask(x, lca), ask(y, lca)));
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值