A — Wormhole Sort
题目大意:
农夫约翰的奶牛已经厌倦了他每天早上离开牲口棚前要它们自己整理的要求。奶牛他们刚刚完成了量子物理学博士学位,准备加快速度。
今天早上,和往常一样,农夫John的N头奶牛(1≤N≤105),编号为1…N,分散在谷仓的N个不同的位置,也编号为1…N,这样奶牛i就在pi的位置。但今天上午有M个虫洞(1≤M≤105),编号1…M,其中虫洞i双向连接位置ai和位置bi,且宽度wi(1≤ai,bi≤N,ai≠bi,1≤wi≤109)。
在任何时间点,位于虫洞两端的两头奶牛可以选择同时通过虫洞交换位置。奶牛必须执行这样的交换,直到奶牛i到达位置i, 1≤i≤N。
奶牛们并不渴望被虫洞压扁,请帮助他们算出排序他们自己时,通过的最小虫洞的最大值。数据可以保证奶牛可以排序自己满足题意。
输入
第一行包含两个整数,N和M。
第二行包含N个整数p1 p2…pN。它保证p是1…N的一个排列。
对于1和M之间的每个i,行i+2包含整数ai、bi和wi。
输出
单个整数:牛在排序过程中必须挤进的最小虫洞宽度的最大值。如果奶牛不需要任何虫洞来分类,则输出- 1。
题目分析:
有一虫洞能位置a,b互通,就像是图论的边。而所通过的虫洞的最小值也就是所有通过的边的最小值。如果这个最小值确定,那么可以确定这个图有几条边,检查每个点是否可以通过这些边到达指定位置,运用并查集可以实现这一点(同个祖先即可以到达指定位置)。显然,这个最小值可以从最大边开始遍历,但直接遍历会超时,用二分可以ac。
注意:在使用并查集时,因为会多次使用查找祖先的函数,如果不路径压缩,会重复很多已经遍历的祖先,结果会超时。
代码实现:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 100007;
int n,m;
int p[maxn];
int fa[maxn];
struct node
{
int a;
int b;
int w;
}wormhole[maxn]; //虫洞
void init() //fa[]的初始化
{
for(int i=0; i<=n; i++) fa[i] = i;
}
bool cmp(node x, node y)
{
return x.w>y.w;
}
//并查集
int fin(int x)
{
if(fa[x]==x) return x;
fa[x] = fin(fa[x]); //路径压缩
return fa[x];
}
void uni(int x, int y)
{
int fx = fin(x);
int fy = fin(y);
if(fx!=fy) fa[fx] = y;
}
bool judge(int x)
{
init();
for(int i=0; i<=x; i++) uni(wormhole[i].a,wormhole[i].b);
bool cnt = true;
for(int i=1; i<=n && cnt; i++)
{
if(p[i]==i) continue;
int fi = fin(i);
int fp = fin(p[i]);
if(fi!=fp) cnt = false; //不能通达
}
return cnt;
}
int main()
{
cin >> n >> m;
bool ifno = true; //已排序?
for(int i=1; i<=n; i++)
{
scanf("%d",&p[i]);
if(p[i]!=i) ifno = false;
}
for(int i=0; i<m; i++)
scanf("%d%d%d",&wormhole[i].a,&wormhole[i].b,&wormhole[i].w);
if(ifno)
{
printf("-1\n");
return 0;
}
sort(wormhole,wormhole+m,cmp);
init();
//二分
int left = 0, right = m-1;
while(right-left>1)
{
int mid = (left+right)>>1;
if(judge(mid)) right = mid;
else left = mid;
}
printf("%d\n",wormhole[right].w);
return 0;
}