BZOJ 4883: [Lydsy1705月赛]棋盘上的守卫 最小生成树 + 建模

Description

在一个n*m的棋盘上要放置若干个守卫。对于n行来说,每行必须恰好放置一个横向守卫;同理对于m列来说,每列
必须恰好放置一个纵向守卫。每个位置放置守卫的代价是不一样的,且每个位置最多只能放置一个守卫,一个守卫
不能同时兼顾行列的防御。请计算控制整个棋盘的最小代价。

Input

第一行包含两个正整数n,m(2<=n,m<=100000,n*m<=100000),分别表示棋盘的行数与列数。
接下来n行,每行m个正整数
其中第i行第j列的数w[i][j](1<=w[i][j]<=10^9)表示在第i行第j列放置守卫的代价。

Output

输出一行一个整数,即占领棋盘的最小代价。

每个行和每个列至少都要有一个守卫只对它起贡献.

而每个首位有可能对这个行/这个列起贡献,很难直接决策应该贡献给哪个

我们发现这其实是一个基环树森林(将行和列的编号看成点),因为每个点要对应唯一的一条边

跑一遍 kruskal 即可

#include <cstdio>
#include <algorithm> 
#include <string> 
using namespace std;  
typedef long long ll; 
const int maxn=200003;   
namespace IO { 
    inline void setIO(string s) {
        string in=s+".in"; 
        freopen(in.c_str(),"r",stdin); 
    }
};  
struct Edge { 
    int u,v; 
    ll c; 
}ed[maxn];     
bool cmp(Edge a,Edge b) {
    return a.c < b.c; 
}
int p[maxn],tag[maxn];   
int find(int x) {
    return p[x]==x?x:p[x]=find(p[x]);    
}
int main() {
    // IO::setIO("input"); 
    int n,m,i,j,edges=0; 
    scanf("%d%d",&n,&m);  
    for(i=1;i<=n+m+1;++i) p[i]=i;   
    for(i=1;i<=n;++i) for(j=1;j<=m;++j) {
        ++edges;    
        scanf("%lld",&ed[edges].c),ed[edges].u=i,ed[edges].v=j+n;           
    }     
    sort(ed+1,ed+1+edges,cmp);  
    ll ans=0;        
    for(i=1;i<=edges;++i) {
        int x=ed[i].u,y=ed[i].v; 
        ll val=ed[i].c;   
        x=find(x),y=find(y); 
        if(x==y && !tag[x]) tag[x]=1, ans+=val;                                                   
        else if(x!=y&&(!tag[x]||!tag[y])) ans+=val, p[x]=y, tag[y]|=tag[x];   
    } 
    printf("%lld\n",ans); 
    return 0; 
}

  

posted @ 2019-08-03 13:22  EM-LGH  阅读(214)  评论(0编辑  收藏  举报