棋盘上的守卫 并查集模拟外向基环树

棋盘上的守卫

基环树就是n个点n条边的树,每个点的入度为1就是外向基环树因为这样的话这个图是往外扩张的,反之内向。

然后这个树自然就只且只有一个环。

题意:n行m列,要求选n个守卫守卫n个行,m个守卫守卫m个列,守卫不能重复,且每个守卫只能守卫行或列,每个守卫一个价值,求最小的代价。

思路:将关于点的图转化为关于边的图,构造一个图含有n+m个点表示n行和m列,题目给的图上的每个点的价值就可以代表点到点的边权了,可以想到如果要覆盖所有的点,就是满足条件的方案,这样的话这个图就是一个每个点都是入度为1的基环树,然后可以给边从小到大排个序,通过并查集模拟生成一个代价最小的外向基环树就行了。对于基环树,可以发现不在乎一个边所指的方向,只要选的边所产生的点集构成的环满足只有一个就好了,所以只需要并查集判断点所在的集合,开个huan[]用来判断一个点所在的集合是否产生了环,合并时对huan取并集就可以了。也可能一个,而是几个独立的基环树

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+10;
int fa[2*maxn];
struct note
{
    int x,y,z;
} e[2*maxn];
int cmp(note a,note b)
{
    return a.z<b.z;
}
int huan[2*maxn];
int getf(int x)
{
    return fa[x]=fa[x]==x?x:getf(fa[x]);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    int cnt=0;
    for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++)
        {
            int t;
            scanf("%d",&t);
            note tt;
            tt.x=i+m;
            tt.y=j;
            tt.z=t;
            e[++cnt]=tt;
        }
    for(int i=1; i<=n+m; i++)
        fa[i]=i;
    sort(e+1,e+1+cnt,cmp);
    ll ans=0;
    for(int i=1; i<=cnt; i++)
    {
        int x,y,z;
        x=e[i].x;
        y=e[i].y;
        z=e[i].z;
        int t1,t2;
        t1=getf(x);
        t2=getf(y);
        if(t1!=t2&&(huan[t1]==0||huan[t2]==0))
        {
            fa[t2]=t1; // 这里注意一下:是将t2放到了t1的圈子里,下面才有t1去和t2取并集 
            huan[t1]|=huan[t2];
            ans=ans+z;
        }
        if(t1==t2&&huan[t1]==0)
        {
            ans=ans+z;
            huan[t1]=1;
        }
    }
    printf("%lld",ans);
}
原文地址:https://www.cnblogs.com/dongdong25800/p/11266579.html