Luogu P1525 [NOIp2010提高组]关押罪犯 | 并查集

题目链接

这一道题,我用了并查集来做。

在此题中,并查集的作用就是:将同一个监狱里的罪犯合并到一起。
思路:将每对罪犯之间的怨气值从大到小排序,再依次把他们分到不同的两个监狱里,当发现这一对罪犯已经在同一个监狱里时,就说明他们已经不能再分开了(分开了就不是最优了,仔细想想为什么)。此时,这一对罪犯之间的怨气值就是答案。值得注意的是,当没有冲突发生时,要记得输出0。那么,我们应该如何用并查集实现以上的思路呢?首先说几个关键词语的含义:
1、“敌人”:如果一对罪犯被分到两个不同的监狱里,那么他们就互为“敌人”。
2、“朋友”:如果一对罪犯被分到一个监狱里,那么他们就互为“朋友”。
需要明确的一点是“敌人”的“敌人”,就是“朋友”。

当分开一对罪犯时(假设他们的名字叫x和y),若x还没有“敌人”,那么就记录y为x的“敌人”(因为他们被分开了嘛);否则就把y所在的集合与x的“敌人”所在的集合合并,这是因为x是y的“敌人”,所以x的“敌人”就是y的“敌人”的“敌人”(即“朋友”),可以发现x的“敌人”和y一定是关在同一监狱里的,然后再对y的“敌人”也这样处理一遍就行了。这样我们就达到了把同一个监狱里的罪犯合并到一起的目的,处理时若发现此时处理的这一对罪犯已经在同一集合中了,则输出他们之间的怨气值作为答案。

#include<iostream>
#include<cstdio>
#include<algorithm>
    using namespace std;
struct edge//存罪犯之间的怨气关系 
{
    int u;
    int v;
    int w;
}e[100005];
bool cmp(edge x,edge y)//将怨气值从大到小排序的排序函数 
{
    return x.w>y.w;
}
    //f为用作实现并查集的数组,表示的是第i个罪犯的“祖先”是谁,enemy储存的是第i个罪犯的某个敌人 
    int f[20005],enemy[20005];
int find_(int x)//并查集查找函数 
{
    if(f[x]==x) return x;
    return f[x]=find_(f[x]);
}
void merge_(int x,int y)//并查集合并函数 
{
    int t1=find_(x),t2=find_(y);
    if(t1!=t2) f[t2]=t1;
    return;
}
int main()
{
    int n=0,m=0;
    scanf("%d%d",&n,&m); 
    for(int i=1;i<=n;i++) f[i]=i;//并查集初始化 
    for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
    sort(e+1,e+m+1,cmp);//将怨气值从大到小排序 
    for(int i=1;i<=m;i++)
    {
        if(find_(e[i].u)==find_(e[i].v))//如果这一对罪犯已经在同一个监狱里 
        {
            printf("%d",e[i].w);//直接输出他们之间的怨气值 
            return 0;//结束程序 
        }
        //如果这一对罪犯仍能分开 
        if(!enemy[e[i].u]) enemy[e[i].u]=e[i].v;//如果u还没有敌人,那么v就是他的敌人 
            else merge_(e[i].v,enemy[e[i].u]);//否则,就将v与u的敌人合并 
        if(!enemy[e[i].v]) enemy[e[i].v]=e[i].u;//如果v还没有敌人,那么u就是他的敌人 
            else merge_(e[i].u,enemy[e[i].v]);//否则,就将u与v的敌人合并 
    }
    printf("%d",0);//记得输出0 
    return 0;
}
原文地址:https://www.cnblogs.com/wozaixuexi/p/8453557.html