【題解】BZOJ4883: [Lydsy1705月賽]棋盤上的守衛(最小生成基環森林)
神題
我的想法是,每行每列都要有匹配且一個點只能匹配一個,於是就把格點和每行每列建點出來做一個最小生成樹,但是不幸的是,這樣子無法控制一個點是否選擇多次,並且無法控制那些不需要變成守衛的點的情況
然後我看了題解..
一個元素的兩種狀態可以對應上一條邊的方向,現在問題就變成了要選出一些邊使得所有點的入度爲1。也就是一個外向基環森林,直接類似克魯斯卡爾做就行了。
這貌似可以抽象成一種模型,也就是有待選點,匹配點,待選點匹配點只能選擇且必須選擇一個待選點,一個待選點只能選擇一個匹配點,同一個點任意選擇匹配代價一樣。若可以接受\(O(\prod P_i)\)的複雜度其中\(P\)代表一種匹配點的個數,那麼就可以這樣考慮給邊定向構成外向基環森林來做。
//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std; typedef long long ll;
inline int qr(){
register int ret=0,f=0;
register char c=getchar();
while(!isdigit(c))f|=c==45,c=getchar();
while(isdigit(c)) ret=ret*10+c-48,c=getchar();
return f?-ret:ret;
}
const int maxn=1e6+5;
struct E{
int u,v,w;
inline bool operator <(const E&a){return w<a.w;}
};
vector<E> e;
int n,m,cnt;
ll w;
inline void add(const int&fr,const int&to,const int&w){e.push_back({fr,to,w});}
int r[maxn*3],siz[maxn*3],c[maxn*3];
inline int q(const int&x){
int t=x,i=x,temp;
while(t^r[t]) t=r[t];
while(i^r[i]) temp=r[i],r[i]=t,siz[t]+=siz[i],siz[i]=0,c[t]|=c[i],c[i]=0,i=temp;
return t;
}
inline void J(int x,int y){
if(siz[q(x)]>siz[q(y)])swap(x,y);
r[q(x)]=q(y); q(x);
}
int main(){
#ifndef ONLINE_JUDGE
freopen("cpp.in","r",stdin);
freopen("cpp.out","w",stdout);
#endif
n=qr(); m=qr();
cnt=n+m;
for(int t=1;t<=n;++t)
for(int i=1,u;i<=m;++i)
u=qr(),add(t,i+n,u);
for(int t=1;t<=cnt;++t) r[t]=t,siz[t]=1;
sort(e.begin(),e.end());
for(int t=0,ed=e.size();t<ed;++t){
int u=q(e[t].v),v=q(e[t].u);
if(!c[u]||!c[v]){
//printf("%d %d\n",u,v);
if(u==v) c[u]=c[v]=1;
J(u,v),w=w+e[t].w;
}
}
printf("%lld\n",w);
cerr<<"w = "<<w<<endl;
return 0;
}