题意:
给定一个H行W列的矩阵,在矩阵的格点上放带权值的卡片(一个点上能放多张)。
现在从每行每列各拿走一张卡片(没有可以不拿),求可以拿到的最大权值。
卡片数N<=1e5,H,W<=1e5
思路:
显然可以构造成一个最大费用流模型:每张卡片到它对应的行列各有一条费用0,容量1的边;源点到每张卡片有一条费用为卡片权值,容量1的边;每个行列到汇点有一条费用0,容量1的边。但是边数有5e5,应该会超时吧?
观察这个图发现除去源点和汇点是一张二分图,想到是否可以利用二分图的性质简化问题。
手动模拟一波发现好像只要贪心地从大到小拿,能拿则拿,那么就能得到最佳答案。如何检测是否还能拿呢?
HALL定理:如果一个二分图上,左部|X|<=右部|Y|,如果左部点的任意一个子集U,U相连边对应右部的子集V都有|U|<=|V|,那么这个二分图有最大匹配|X|。
每次拿走点相当于在U中增加点,在V中增加边,用并查集维护一下集合的大小即可。
- 所在行列在同个集合中,直接判断这个集合是否满足条件
- 所在行列不在同一个集合中,那么判断这两个集合合并后是否满足条件,若是则合并
#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);
}