【[Offer收割]编程练习赛23 D】【最小生成树+set的启发式合并】观光旅行

题目4 : 观光旅行

时间限制: 10000ms
单点时限: 1000ms
内存限制: 256MB

描述

小Hi去H市旅游,H 市有 n 个旅游景点,有 m 条双向道路连接这些旅游景点,使得任意两个景点之间都至少有一条路径可以到达。每条道路都会有一个不同的正整数 w 描述这条道路的拥挤系数。小Hi非常讨厌拥挤的感觉,因此当小Hi尝试从景点 u 去到景点 v 的时候,总会尽可能地选择一条路径,使得这条路径中最大的拥挤系数是所有 u 到 v 的路径中最小的。小Hi会对这条路径中最拥挤的道路印象深刻,并且认为它是景点 u 和景点 v 的关键道路。

现在小Hi想要知道,对于H市中的每一条道路,是否存在两个景点 u 和 v,使得它是 u 和 v 的关键道路。

输入

第一行输入两个正整数 n 和 m,分别表示 H 市的景点数量和道路数量。

接下来 m 行,第 i 行输入三个正整数 ui, vi 和 wi,表示有一条连接 ui 和 v的双向道路,它的拥挤系数为 wi,保证没有两条道路的拥挤系数是相同的。

2 ≤ n ≤ 105, n-1 ≤ m ≤ 2 × 105, 1 ≤ ui, vi ≤ n, 1 ≤ wi ≤ 109, wi ≠ wj(i ≠ j)

输出

输出共 m 行,第 i 行输出用一个空格隔开的两个整数 x 和 y,满足 x < y 且输入的第 i 条边是 x 和 y 的关键道路。如果不存在满足要求的 x 和 y,则输出“0 0”(不含引号)。如果有多个满足条件的 x 和 y,输出其中 x 最大的,如果还有多个满足条件的,输出其中 y 最小的。

样例输入
6 7
1 2 6
1 3 2
2 3 3
2 4 1
3 6 9
3 5 8
4 6 4
样例输出
0 0
1 3
3 4
2 4
0 0
5 6
4 6

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x, y) memset(x, y, sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; }
const int N = 2e5 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f;
template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; }
int casenum, casei;
int n, m;
struct Edge
{
	int x, y, z, o;
	bool operator < (const Edge & b)const
	{
		return z < b.z;
	}
}edge[N];

int f[N];
int sz[N];
set<int>sot[N];

int find(int x)
{
	return f[x] == x ? x : f[x] = find(f[x]);
}

pair<int, int>ans[N];
void update(int o, int x, int y)
{
	if (!sot[x].size())return;
	if (!sot[y].size())return;
	int bigx = *--sot[x].end();
	set<int>::iterator it = sot[y].lower_bound(bigx);
	if (it == sot[y].begin())return;

	int py = *--it;
	int px = *sot[x].lower_bound(py);

	swap(px, py);
	if (px > ans[o].first || px == ans[o].first && py < ans[o].second)
	{
		ans[o] = { px,py };
	}
}
int main()
{
	while(~scanf("%d%d", &n, &m))
	{
		for (int i = 1; i <= n; ++i)
		{
			f[i] = i;
			sz[i] = 1;
			sot[i].clear();
			sot[i].insert(i);
		}
		for (int i = 1; i <= m; ++i)
		{
			int x, y, z;
			scanf("%d%d%d", &x, &y, &z);
			edge[i] = { x,y,z,i };
			ans[i] = { 0, 0 };
		}
		sort(edge + 1, edge + m + 1);
		for (int i = 1; i <= m; ++i)
		{
			int x = edge[i].x;
			int y = edge[i].y;
			int fx = find(x);
			int fy = find(y);
			if (fx == fy)continue;

			//fy -> fx
			if (sz[fx] < sz[fy])
			{
				swap(fx, fy);
				swap(x, y);
			}

			update(edge[i].o, fx, fy);
			update(edge[i].o, fy, fx);

			//合并操作
			for (auto it : sot[fy])sot[fx].insert(it);
			sot[fy].clear();
			f[fy] = fx;
			sz[fx] += sz[fy];
		}

		for (int i = 1; i <= m; ++i)
		{
			printf("%d %d\n", ans[i].first, ans[i].second);
		}
	}
	return 0;
}
/*
【题意】
http://hihocoder.com/contest/offers23/problem/4
小Hi去H市旅游,H 市有 n 个旅游景点,有 m 条双向道路连接这些旅游景点,使得任意两个景点之间都至少有一条路径可以到达。
每条道路都会有一个不同的正整数 w 描述这条道路的拥挤系数。小Hi非常讨厌拥挤的感觉,因此当小Hi尝试从景点 u 去到景点 v 的时候,总会尽可能地选择一条路径,使得这条路径中最大的拥挤系数是所有 u 到 v 的路径中最小的。
小Hi会对这条路径中最拥挤的道路印象深刻,并且认为它是景点 u 和景点 v 的关键道路。
现在小Hi想要知道,对于H市中的每一条道路,是否存在两个景点 u 和 v,使得它是 u 和 v 的关键道路。
如果有多个满足条件的 x 和 y,输出其中 x 最大的,如果还有多个满足条件的,输出其中 y 最小的。

【分析】
显然,如果我们考虑从任意两点间的最小拥挤系数的街道,我们会考虑从小到大的顺序逐渐加边。
这样子,连通性会形成一棵树,而且是最小生成树。
而任意两点的联通所需要的成本,则是这两个点连通性达成时的最后一条边。

也就是说,只有最小生成树的树边可能是关键道路。
而我们需要找到树边两侧的{x, y},使得x <= y,且在x尽可能大的条件下,y尽可能小。

首先,当一条边确定为树边的时候,这条边的影响,是合并了2个连通块,这里可以通过set的启发式合并维持复杂度。
然后,考虑{x,y}的选择。显然第一关键字是x,于是——
1,先在集合A中找到最大的y_,使得对x的限制尽可能小。
2,再在集合B中找到比y_刚好小的最大的x,这是确定的第一关键字
3,然后在集合A中找到比x刚好大的数y,这就是确定的第二关键字。

【时间复杂度&&优化】
O(nlognlogn)

*/


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值