Card Collector AtCoder

题意:

给定一个H行W列的矩阵,在矩阵的格点上放带权值的卡片(一个点上能放多张)。

现在从每行每列各拿走一张卡片(没有可以不拿),求可以拿到的最大权值。

卡片数N<=1e5,H,W<=1e5

思路:

显然可以构造成一个最大费用流模型:每张卡片到它对应的行列各有一条费用0,容量1的边;源点到每张卡片有一条费用为卡片权值,容量1的边;每个行列到汇点有一条费用0,容量1的边。但是边数有5e5,应该会超时吧?

观察这个图发现除去源点和汇点是一张二分图,想到是否可以利用二分图的性质简化问题。

手动模拟一波发现好像只要贪心地从大到小拿,能拿则拿,那么就能得到最佳答案。如何检测是否还能拿呢?

HALL定理:如果一个二分图上,左部|X|<=右部|Y|,如果左部点的任意一个子集U,U相连边对应右部的子集V都有|U|<=|V|,那么这个二分图有最大匹配|X|。

每次拿走点相当于在U中增加点,在V中增加边,用并查集维护一下集合的大小即可。

  1. 所在行列在同个集合中,直接判断这个集合是否满足条件
  2. 所在行列不在同一个集合中,那么判断这两个集合合并后是否满足条件,若是则合并
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
struct node{
    int r,c,a;
    friend bool operator<(node a,node b){
        return a.a>b.a;
    }
}st[maxn];
int fa[maxn],S[maxn],T[maxn];
int n,H,W;
int init(){
    for(int i=1;i<=H+W;i++){
        fa[i]=i;
        T[i]=1;
    }
}
int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
int main(){
    cin>>n>>H>>W;
    init();
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&st[i].r,&st[i].c,&st[i].a);
    }
    sort(st+1,st+1+n);
    ll ans=0;
    for(int i=1;i<=n;i++){
//        printf("%d %d %d
",st[i].r,st[i].c,st[i].a);
        int fr=find(st[i].r),fc=find(H+st[i].c);
        if(fr==fc){//两个在同一集合内
            if(T[fc]>=S[fc]+1){
                S[fc]++;
                ans=ans+st[i].a;
            }
        }
        else{//两个不在同一集合内,要合并
            if(T[fr]+T[fc]>=S[fr]+S[fc]+1){
                fa[fr]=fc;
                T[fc]+=T[fr];
                S[fc]=S[fr]+S[fc]+1;
                ans=ans+st[i].a;
            }
        }
    }
    printf("%lld
",ans);
}
原文地址:https://www.cnblogs.com/ucprer/p/11800975.html