【BZOJ1433】[ZJOI2009] 假期的宿舍(二分图匹配入门)

点此看题面

**大致题意:**有 n n n个学生,其中一部分是在校学生,一部分不是,而在校学生中一部分回家,一部分不回家,并且我们用一个01矩阵表示学生之间相互认识关系。已知每个学生只能睡自己认识的人的床(当然,他也可以睡自己的床),问是否有一个方案使得所有学生都有床睡。


建图

这道题是一道图论题。对于这种图论题,我们首先要考虑的便是建图。

不难想到,我们可以将每个人与其能睡的床连一条边,即:

  • 对于一个在校不回家的学生 i i i,我们将 i i i与自己的床连一条边。
  • 对于一个在校且不回家不在校的学生 i i i,如果他认识一个在校的学生 j j j,我们将 i i i j j j的床连一条边。

之所以上面要强调不回家,是因为对于回家的学生,在给他连边是没有任何意义的。

而这张图建成之后,应该不难发现它是一张二分图,那么原题就变成了一道求二分图最大匹配的题目,就可以用匈牙利算法来解决了。

L i n k Link Link

二分图的概念以及匈牙利算法详见博客二分图匹配:匈牙利算法

再看一眼数据范围, n ≤ 50 n≤50 n50,那么匈牙利算法的 O ( n 2 ) O(n^2) O(n2)复杂度在这道题目中不是轻松跑跑吗?


代码

#include
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define LL long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define tc() (A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++)
#define N 50
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
char ff[100000],*A=ff,*B=ff;
using namespace std;
int n,m,ee=0,lnk[N+5],vis[N+5],s[N+5],SchoolStudent[N+5],BackHome[N+5];
struct edge
{
    int to,nxt;
}e[N*N+5]; 
inline void read(int &x)
{
    x=0;static char ch;
    while(!isdigit(ch=tc()));
    while(x=(x<<3)+(x<<1)+ch-48,isdigit(ch=tc()));
}
inline bool GetPoint(int x,int t)//为编号为x的点寻找一个匹配
{
    register int i;
    for(i=lnk[x];i;i=e[i].nxt)//枚举每一个与x相邻的节点
    {
        if(!(vis[e[i].to]^t)) continue;//如果这个节点已经访问过了,就跳过
        vis[e[i].to]=t;//否则,标记这个节点已访问
        if(!s[e[i].to]||GetPoint(s[e[i].to],t))//如果这个节点没被匹配,或者与这个节点匹配的节点能找到一个新的节点匹配
        {
            s[e[i].to]=x;//标记这个节点与x匹配
            return true;//找到一个匹配,返回true
        }
    }
    return false;//说明找不到匹配,返回false
} 
int main()
{
    register int i,j,T,x,ok;read(T);
    while(T--)
    {
        for(read(n),ee=0,ok=i=1;i<=n;++i) vis[i]=s[i]=lnk[i]=0;//多组数据,记得初始化
        for(i=1;i<=n;++i) read(SchoolStudent[i]);//读入每个学生是否是在校学生
        for(i=1;i<=n;++i) {read(BackHome[i]);if(!SchoolStudent[i]) BackHome[i]=0;}//读入每个学生是否回家,如果不是在校学生,默认其不回家
        for(i=1;i<=n;++i)
        {
            for(j=1;j<=n;++j)
            {
                read(x);
                if((x&&!BackHome[i]&&SchoolStudent[j])||(i==j&&SchoolStudent[i]&&!BackHome[i])) add(i,j);//如果i认识j,i不回家,且j是在校学生;或者i=j,i是在校学生,且i不回家,将i与j连一条边
            }
        }
        for(i=1;i<=n;++i) if(!BackHome[i]&&!GetPoint(i,i)) {ok=0;break;}//如果某个学生不回家,且找不到床,那么就说明没有使每个人都有床的方案
        puts(ok?"^_^":"T_T");
    }
    return 0;
}

你可能感兴趣的:(BZOJ,匈牙利算法)