[BZOJ4883][Lydsy1705月赛]棋盘上的守卫[最小基环树森林]

题意

有一大小为 \(n*m\) 的棋盘,要在一些位置放置一些守卫,每个守卫只能保护当前行列之一,同时在每个格子放置守卫有一个代价 \(w\) ,问要使得所有格子都能够被保护,需要最少多少的代价。

\(2\leq n,m\leq 10^5\ ,n*m\leq 10^5\)

分析

  • 将行列看成 \(n+m\) 个点。将每个格点放置守卫看成所在行列连了一条边,然后把每条边定向,如果被指向表示当前格点对当前 行/列 进行了保护。

  • 这样就会有 \(n+m\) 个点,\(n+m\) 条有向边,同时每条边最多有 1 的入度。形成了基环树森林。

  • 最小基环树森林可以通过 \(Kruskal\) 贪心求解,证明仍然可以考虑反证法。

  • 总时间复杂度为 \(O(n)\)

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5 + 7;
int n,m,edc;
int par[N],c[N];
struct edge{
    int last,to,dis;
    edge(){}edge(int last,int to,int dis):last(last),to(to),dis(dis){}
    bool operator <(const edge &rhs)const{
      return dis<rhs.dis;   
    }
}e[N*2];
int getpar(int a){return par[a]==a?a:par[a]=getpar(par[a]);}
LL Kruskal(){
    sort(e+1,e+1+edc);int cnt=0;LL res=0;
    for(int i=0;i<N;i++) par[i]=i;
    for(int i=1;i<=edc;i++){
        int x=getpar(e[i].last),y=getpar(e[i].to);0
        if(x==y&&!c[x]) c[x]=1,cnt++,res+=e[i].dis;
        if(x!=y&&!(c[x]&&c[y])) par[x]=y,c[y]|=c[x],cnt++,res+=e[i].dis;
        if(cnt==n+m) return res;
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    for(int j=1,x;j<=m;j++){
        scanf("%d",&x);
        e[++edc]=edge(i,j+n,x);
    }
    printf("%lld\n",Kruskal());
    return 0;
}

转载于:https://www.cnblogs.com/yqgAKIOI/p/9804109.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值