【无码专区1】简单路径的第二大边权(启发式合并+最小生成树)

只有std,没有自我实现,所以叫做无码专区

description

给一张无向图,多次询问,每次询问两个点之间所有简单路径(不重复经过点)中边权第二大(不是严格第二大)的权值的最小值。

数据范围:10510^5105 级别

我的想法

50%50\%50% 的数据 q,n≤103,m≤2×103:q,n\le 10^3,m\le 2\times 10^3:q,n103,m2×103:

先做一次最小生成树,求出任意两点之间联通的最小边权(某条路径的最大边权值)。

每次询问 (u,v)(u,v)(u,v) ,我直接枚举中间转折点 iii,强制这条路径是 u→i→vu\rightarrow i\rightarrow vuiv。【→\rightarrow 代指一条路径】

第二大边权就是 (u,i)(u,i)(u,i) 联通路径的最大值和 (v,i)(v,i)(v,i) 联通路径的最大值,二者中的较小值。

旁边的 Oxide\text{Oxide}Oxide 巨佬认为很有可能 u→iu\rightarrow iuii→wi\rightarrow wiw 之间经过了同样的点。

i.e. u→x→i→x→vu\rightarrow x\rightarrow i \rightarrow x\rightarrow vuxixv

但后面再仔细一想,就算这是的答案会被 iii 更新,但是后面一定会枚举到 xxx,显然u→x→vu\rightarrow x\rightarrow vuxv 会比以前的路径少了 (x,i)(x,i)(x,i) 一段。

路径上的边权最大值一定是不减的

所以多的 (x,i)(x,i)(x,i) 一段只可能使最大边权增大 / 不变。

那么 xxx 的决策一定是不劣于 iii 的决策的。

另有 20%20\%20% 是树:两个点之间只有一条简单路径,可以直接倍增求路径的第二大边权值。

综上,本题自我实现分值应该在 70′70'70

solution

类似最小生成树的方法,从小到大加边。

如果加完一条边后,u,vu,vu,v 之间只差一条边联通,那么显然这条边就是第二小,也就是最终的答案。

考虑怎么维护?

N(u):N(u):N(u):uuu 有直接边相连,但还没有相连的点的集合【当前枚举边的权值暂时小于等于这些点与 uuu 的权值,最小生成树的写法就还没有加入这些边】

或者理解为:还差一条边就能联通的点的集合

考虑启发式合并,每次合并 u,vu,vu,v 各自所在的连通块。

此时可能出现:N(u)N(u)N(u) 中的点 xxxvvv 相连【在 vvv 连通块里面】 ,或,N(v)N(v)N(v) 中的点 yyyuuu 相连【在 uuu 连通块里面】

这个时候意味着,加上 u−vu-vuv 这条边后,还差 u−xu-xuxv−yv-yvy 这一条边就会使得 u,vu,vu,v 相连,所以 u−vu-vuv 这条边权就是最后的答案。

如果直接枚举 N(u),N(v)N(u),N(v)N(u),N(v) 则不符合时间限制。

我们可以这么做:

  • 遍历 N(u)N(u)N(u) 的所有点,然后看是否在 vvv 的询问中。

    i.e. 假设 x∈N(u),x∈q(v)x\in N(u),x\in q(v)xN(u),xq(v)x−ux-uxu 之间的边还没有加入。

    此时加入为 u−vu-vuv 的边。一旦加入完,x→u→vx\rightarrow u\rightarrow vxuv就只差 x−ux-uxu 的一条边。

    所以答案就是现在操作的 u−vu-vuv 边的边权。

    这样就处理了挂在 vvv 上面的某些 通过 uuu 连通块中某些点和边解决 的询问。

  • 遍历 uuu 里面的所有询问,判断是否在 N(v)N(v)N(v) 中。

    i.e. 假设 x∈q(u)x\in q(u)xq(u)x−vx-vxv 之间的边还没有加入。

    此时加入为 u−vu-vuv 的边。一旦加入完,x→v→ux\rightarrow v\rightarrow uxvu 就只差 x−vx-vxv 的一条边。

    所以答案是 u−vu-vuv 现在这条边的边权。

    这样就处理了挂在 uuu 上面的某些 通过 vvv 连通块中某些点和边解决 的询问。

这么做会发现,虽然是合并两个联通块和处理两个联通块各自挂着的询问,但是枚举的只有一个联通块的信息。

所以启发式合并,就用 N(u)+q(u)N(u)+q(u)N(u)+q(u)N(v)+q(v)N(v)+q(v)N(v)+q(v) 大小比较,选较小的进行枚举。

时间复杂度 O(nlog⁡n)O(n\log n)O(nlogn)

合并具体而言:枚举其中一个较小连通块的信息,进行答案处理。所有挂在 u,vu,vu,v 点的询问和边都重新挂在合并后新连通块的根 www 上。

i.e. 询问 u,xu,xu,x 的答案,合并后相当于问 w,xw,xw,x 的答案,因为反正 u−wu-wuw 的边权不是第二大。原本 u−xu-xux 的一条边,变成 w−xw-xwx 的一条边。

所以上面的形如 x−ux-uxu :不一定表示原先加入的 mmm 条边就是 x−ux-uxu,而是可能通过 x−a−b−c−...−ux-a-b-c-...-uxabc...u ,不断合并,可能代指的是一条简单路径。

参考code

#include <bits/stdc++.h>
using namespace std;

#define N 400005

#define pb push_back
int fa[N];
struct node {
	int x, y, z;
} b[N];
int ans[N], n, m, Q;
set<array<int, 2>> q[N];
set<int> e[N];

int get(int x) {
	if (fa[x] == x)
		return x;
	return fa[x] = get(fa[x]);
}

inline bool cmp(node x, node y) {
	return x.z < y.z;
}

void combine(int x, int y, int val) {
	for (auto u : e[x]) {
		while (1) {
			auto it = q[y].lower_bound({u, -1});
			if (it == q[y].end() || (*it)[0] != u)
				break;
			int id = (*it)[1];
			ans[id] = val;
			assert(q[y].count({u, id}));
			assert(q[u].count({y, id}));
			q[y].erase(it);
			q[u].erase({y, id});
		}
	}
	vector<array<int, 2>> delq;
	for (auto u : q[x]) {
		if (e[y].count(u[0])) {
			ans[u[1]] = val;
			q[u[0]].erase({x, u[1]});
			delq.pb(u);
		}
	}
	for (auto u : delq)
		q[x].erase(u);
	fa[x] = y;
	for (auto v : e[x]) {
		assert(e[v].count(x));
		e[v].erase(x);
		if (v != y) {
			e[v].insert(y);
			e[y].insert(v);
		}
	}
	e[x].clear();
	for (auto v : q[x]) {
		assert(v[0] != y);
		assert(q[v[0]].count({x, v[1]}));
		q[v[0]].erase({x, v[1]});
		q[v[0]].insert({y, v[1]});
		q[y].insert({v[0], v[1]});
	}
	q[x].clear();
}

int main() {
	freopen("path.in", "r", stdin);
	freopen("path.out", "w", stdout);
	scanf("%d%d%d", &n, &m, &Q);
	for (int i = 1; i <= n; i++)
		e[i].clear(), q[i].clear();
	for (int i = 1; i <= m; i++) {
		scanf("%d%d%d", &b[i].x, &b[i].y, &b[i].z);
		e[b[i].x].insert(b[i].y);
		e[b[i].y].insert(b[i].x);
	}
	for (int i = 1; i <= n; i++)
		fa[i] = i;
	sort(b + 1, b + 1 + m, cmp);
	for (int i = 1; i <= Q; i++) {
		ans[i] = 0;
		int x, y;
		scanf("%d%d", &x, &y);
		if (e[x].count(y)) {
			ans[i] = 1;
			continue;
		}
		q[x].insert({y, i});
		q[y].insert({x, i});
	}
	for (int i = 1; i <= m; i++) {
		b[i].x = get(b[i].x), b[i].y = get(b[i].y);
		if (b[i].x == b[i].y)
			continue;
		if (q[b[i].x].size() + e[b[i].x].size() > q[b[i].y].size() + e[b[i].y].size())
			swap(b[i].x, b[i].y);
		combine(b[i].x, b[i].y, b[i].z + 1);
	}
	for (int i = 1; i <= Q; i++)
		printf("%d\n", ans[i] - 1);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值