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

来源:互联网 发布:独立网络经纪人登录 编辑:程序博客网 时间:2024/05/20 23:37

题目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 71 2 61 3 22 3 32 4 13 6 93 5 84 6 4
样例输出
0 01 33 42 40 05 64 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|1typedef 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 -> fxif (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)*/