【題解】BZOJ4883: [Lydsy1705月賽]棋盤上的守衛(最小生成基環森林)

【題解】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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章