题目
思路
将每场考试看成一条边,连接 a i a_i ai 和 b i b_i bi ,那么有如下转化:只有基环树和树满足条件。结论是显然的,因为每场考试(边数)都需要有一天来考,所以不能超过总天数(点数),也就是 ∣ E ∣ ≤ ∣ V ∣ |E|\le |V| ∣E∣≤∣V∣ 。
有了这一点,我们就可以直接用并查集进行判断了。对于一颗树,我们将其根作为该树内权值最大的点(以尽量更早考完所有考试)。对于基环树,我们打一个特殊标记即可,比如将其与 0 0 0 相连。
值域很大怎么办?用个 m a p \tt map map 就完事儿了。复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn) 。注意并查集也是这个复杂度——因为我的实现方法不允许按秩合并。
代码
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MaxN = 1000005;
int val[MaxN<<1]; // 并查集节点->真实值
int ans; // 最终答案
namespace UFS{
int fa[MaxN<<1];
void init(int n){
for(int i=0; i<=n; ++i)
fa[i] = i;
}
inline int find(int a){
if(a == fa[a]) return a;
return fa[a] = find(fa[a]);
}
bool combine(int a,int b){
a = find(a), b = find(b);
if(!a && !b) return false;
if(!a || !b){ // 一个基环一个树
ans = max(ans,val[a+b]);
fa[a+b] = 0; return true;
}
if(a == b){ // 形成自环
ans = max(ans,val[a]);
fa[a] = 0; return true;
}
if(val[a] > val[b])
swap(a,b);
ans = max(ans,val[a]);
fa[a] = b; return true;
}
}
map< int,int > mp;
int main(){
int n = readint(), tot = 0;
UFS::init(n<<1);
for(int i=1; i<=n; ++i){
int a = readint(), b = readint();
if(!mp.count(a))
val[mp[a] = ++ tot] = a;
if(!mp.count(b))
val[mp[b] = ++ tot] = b;
if(!UFS::combine(mp[a],mp[b])){
puts("-1"); return 0;
}
}
printf("%d\n",ans);
return 0;
}