洛谷 P1967 货车运输

5 篇文章 0 订阅

题面

给出一个 n 点 m 边无向图,每一条边上有重量限制,有 q 辆车询问从 u 到 v 的最大运载重量(即路径上的最小重量限制)
1 <= n < 104,1 <= m < 5×104,1 <= q < 3×104,权值z满足0 <= z <= 105

分析

经过判断发现走某一些路是不利的,就像 dijkstra 那里的松弛操作,这里的目的是即便走的路径长,但是路径上最小的权值大就行,这些不利的路可以删去,剩下的就是最大生成树
能在不利边上跑的,一定能在最大生成树上跑,反之不一定,这就作为证明。
用 kruskal 跑出最大生成树即可
可能有多个连通块,所以最后的查询还要借助并查集的 find


现在问题变成了怎么在树上求两点间路径权值的最大值,不难想到一些树上操作如倍增,树链剖分。
这里采用树链剖分,使得每一次查询复杂度在 O(logn)
然后求一系列区间上的最小值即可求出最强的约束条件,也即载重上限

注意是边权树,和往常一样,采取将边权下放到点权的做法,对于树根,节点值赋大于z的上界,这样在取 min 时对数据无影响。

注意,查询时,当两点处于同一个重链上的情况,查询起点 +1,这是因为:

粗线是重链,u 从左下跳上来,因为权值下放,所以 u 上的权是 u 上面那条边的,而 u→v 的权值并不包含它,所以 u → v 的权值实际上只计算t和v的权值,这里的t就是 u+1 的位置
有时也许 u+1 会大于 v 。。。也就是说u从细线直接跳到 v 上了,这种情况 +1 可能会出锅。。。但是实际上AC了。。。这种特例可以考虑特判


之后就是维护剖出来的最大值了,代码上采用了树状数组维护区间最值

代码

#include "cstdlib"
#include <iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<string.h>
#include<string>
using namespace std;
int N, M;//N节点M边
int p[10005];//并查集
struct line//存边
{
	int w, u, v;//权重,起始,结束
}liner[50010];
int A[10005];//用树状数组最大值维护A
int B[10005];//原数组
#define lowbit(x) (x&(-x))
class Binary_Indexed_Trees_max//区间最大值
{
private:
	const static int MAXN = 10010;
	int C[MAXN];
public:
	void updata(int x)
	{
		int lx, i;
		while (x <= N)
		{
			C[x] = A[x];
			lx = lowbit(x);
			for (i = 1; i < lx; i <<= 1)
				C[x] = min(C[x], C[x - i]);
			x += lowbit(x);
		}
	}
	int query(int x, int y)
	{
		int ans = 1000000;
		while (y >= x)
		{
			ans = min(A[y], ans);
			y--;
			for (; y - lowbit(y) >= x; y -= lowbit(y))
				ans = min(C[y], ans);
		}
		//C[y]是[ y-lowbit(y)+1 , y ]内的最大值
		//y-lowbit(y) > x ,则query(x,y) = max( C[y] , query(x, y-lowbit(y)) );
		//y-lowbit(y) <=x,则query(x,y) = max( A[y] , query(x, y-1);
		return ans;
	}
}BIT;
void sswap(int& a, int& b)
{
	int t = b;
	b = a;
	a = t;
}
class Tree_Chain {
private:
	const static int MAXN = 10005;
	int fa[MAXN];//当前节点的父节点
	int son[MAXN];//节点的重儿子
	int top[MAXN];//重链顶
	int dep[MAXN];//当前节点深
	int l[MAXN];//dfs序
	std::vector<pair<int, int> >linker[MAXN];//存边
public:
	int dfs_clock = 0;
	int siz[MAXN];//表示其子树的节点数
	void dfs1(int x)//x是当前节点(当前树根)
	{
		int cur;
		siz[x] = 1;
		for (int i = 0; i < linker[x].size(); i++)
		{
			cur = linker[x][i].first;
			if (cur != fa[x]) //不是fa
			{
				B[cur] = linker[x][i].second;
				dep[cur] = dep[x] + 1;//更新dep
				fa[cur] = x;//更新fa
				dfs1(cur);
				siz[x] += siz[cur];//更新siz
				if (siz[cur] > siz[son[x]])son[x] = cur;//更有可能成为重儿子
			}
		}
	}
	void dfs2(int x, int t)//当前节点x,当前重链顶的编号
	{
		l[x] = ++dfs_clock;//更新dfs序
		A[dfs_clock] = B[x];//换序映射
		top[x] = t;
		if (son[x])dfs2(son[x], t);//继续连重链
		int cur;
		for (int i = 0; i < linker[x].size(); i++)
		{
			cur = linker[x][i].first;
			if (cur != fa[x] && cur != son[x])//非父亲非重儿子
				dfs2(cur, cur);//在其他儿子上找更小一些的
		}
	}
	void insert(int& u, int& v, int& w)
	{
		linker[u].push_back(make_pair(v, w));
		linker[v].push_back(make_pair(u, w));
	}
	int Lca(int& u, int& v)
	{
		while (top[u] != top[v])
		{
			if (dep[top[u]] > dep[top[v]])u = fa[top[u]];
			else v = fa[top[v]];
		}
		return dep[u] < dep[v] ? u : v;
	}
	void flush()
	{
		dfs_clock = 0;
		for (int i = 0;i < MAXN;i++)linker[i].clear();
		memset(son, 0, MAXN * sizeof(int));
		memset(dep, 0, MAXN * sizeof(int));
	}
	int calmax(int& u, int& v)
	{
		int ans =1000000;
		while (top[u] != top[v])
		{
			if (dep[top[u]] < dep[top[v]])sswap(u, v);
			ans = min(BIT.query(l[top[u]], l[u]),ans);//l[top[u]]到l[u]是dfs序上连续的一段
			u = fa[top[u]];
		}
		if (dep[u] > dep[v])sswap(u, v);//到了一个重链上,切换到u比v浅的状态
		ans = min(BIT.query(l[u]+1, l[v]),ans);//因为下放边权到点权,所以+1
		return ans;
	}
}TC;
int cmp(line i, line j) { return i.w > j.w; }//用于sort进行从大到小排序,跑最大生成树
int find(int num)//并查集-寻找顶端的函数
{
	while (p[num] != num)num = p[num];
	return num;
}
void Kruskal()
{
	int temp;
	sort(liner, liner + M, cmp);//贪心取
	for (int i = 0; i < M; i++)
	{
		if (find(liner[i].u) != find(liner[i].v))
		{
			temp = find(liner[i].v);
			p[find(liner[i].u)] = temp;//并查集合并
			p[liner[i].u] = temp;//路径压缩
			p[liner[i].v] = temp;

			TC.insert(liner[i].u, liner[i].v, liner[i].w);//插入树链剖分
		}
	}
}
int main()
{
	cin >> N >> M;
	for (int i = 1; i <= N; i++)p[i] = i;
	for (int i = 0; i < M; i++)cin >> liner[i].u >> liner[i].v >> liner[i].w;
	Kruskal();
	for (int i = 1; i <= N; i++)
	{
		if (!TC.siz[i]) { B[i] = 1000000; TC.dfs1(i);TC.dfs2(i, i); }
	}
	for (int i = 1; i <= N; i++)BIT.updata(i);//维护到最大值
	int q,u,v;
	cin >> q;
	for (int i = 0; i < q; i++)
	{
		cin >> u >> v;
		if (find(u) != find(v))cout << -1 << endl;//不在同一个连通块,没有路径
		else {
			cout << TC.calmax(u, v)<<endl;
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值