【[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)

*/


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章