bzoj 4883 棋盘上的守卫 —— 基环树转化

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4883

首先,注意到每个点可横可竖,但花费一样;

所以考虑行列的交集,那么这个条件可以转化为行点和列点之间的边,边权就是花费;

如果行和列都按原图交点连了边,那么问题就转化成在有向图中,每个点(原图的行/列)都有且仅有1入度;

这个性质让我们联想到基环树,所以只需要构造一个基环树森林即可;

又要边权和最小,仿照 Kruskal,从小到大加边,对于连通块,看看是否已经有环即可。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int const xn=2e5+5;
int n,m,ct,fa[xn];
ll ans;
bool v[xn];
struct N{int u,v,w;}ed[xn<<1];
bool cmp(N x,N y){return x.w<y.w;}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
int rd()
{
    int ret=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=0; ch=getchar();}
    while(ch>='0'&&ch<='9')ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar();
    return f?ret:-ret;
}
int main()
{
    n=rd(); m=rd();
    for(int i=1;i<=n;i++)
        for(int j=1,z;j<=m;j++)
        {
            z=rd();
            ed[++ct].u=i; ed[ct].v=j+n; ed[ct].w=z;
        }
    for(int i=1;i<=n+m;i++)fa[i]=i;
    sort(ed+1,ed+ct+1,cmp);
    for(int i=1;i<=ct;i++)
    {
        int x=find(ed[i].u),y=find(ed[i].v),w=ed[i].w;
        if(x==y)
        {
            if(v[x])continue;
            else ans+=w,v[x]=1;
        }
        else
        {
            if(v[x]&&v[y])continue;
            else if(v[x]||v[y])ans+=w,fa[x]=y,v[y]=1;
            else ans+=w,fa[x]=y;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

 

posted @ 2018-09-25 19:23  Zinn  阅读(159)  评论(0编辑  收藏  举报