题解:洛谷P2055/[ZJOI2009] 假期的宿舍(匈牙利算法)

题目描述

学校放假了.....有些同学回家了,而有些同学则有以前的好朋友来探访,那么住宿就是一个问题。比如A和B都是学校的学生,A要回家,而C来看B,C与A不认识。我们假设每个人只能睡♂和自己直接认识的人(的床)。那么一个解决方案就是B睡A(的床)而C睡B(的床)。而实际情况可能非常复杂,有的人可能认识好多在校学生,在校学生之间也不一定都互相认识。我们已知一共有\(n\)个人,并且知道其中每个人是不是本校学生,也知道每个本校学生是否回家。问是否存在一个方案使得所有不回家的本校学生和来看他们的其他人都有地方住。

输入输出格式

输入

第一行一个数\(T\)表示数据组数。接下来\(T\)组数据,每组数据第一行一个数\(n\)表示涉及到的总人数。接下来一行\(n\)个数,第\(i\)个数表示第\(i\)个人是否是在校学生(0表示不是,1表示是)。再接下来一行\(n\)个数,第\(i\)个数表示第\(i\)个人是否回家(0表示不回家,1表示回家,注意如果第\(i\)个人不是在校学生,那么这个位置上的数是一个随机的数,你应该在读入以后忽略它)。接下来\(n\)行每行\(n\)个数,第\(i\)行第\(j\)个数表示\(i\)\(j\)是否认识(1表示认识,0表示不认识,第\(i\)行第\(i\)个值为0,但是显然自己还是可以睡自己的床),认识的关系是相互的。

输出

对于每组数据,如果存在一个方案,则输出"^_^"(不含引号)否则输出"T_T"(不含引号)。(注意输出的都是半角字符,即三个符号的 ASCII 码分别为94,84,95)。

输入输出样例

输入样例
1
3
1 1 0
0 1 0
0 1 1
1 0 0
1 0 0
输出样例
^_^

数据范围

对于\(30\%\)的数据满足\(1 \leq n \leq 12\).

对于\(100\%\)的数据满足\(1 \leq n \leq 50,1\le T\le 20\).

思路

这个问题中我们把\(n\)个人看作集合\(A\),把\(n\)张床看作集合\(B\)(但显然非本校学生没有床位)。如果对第\(i\)个人和第\(j\)张床,它们之间满足以下关系:

1.\(i\)不是本校学生,或者\(i\)是本校学生且假期不回家。

2.\(j\)\(i\)互相认识(包括\(j=i\)的情况),且\(j\)是本校学生(否则没有床位)。

则在\(A_i,B_j\)之间连接一条边:

这样我们就构造了一张二部图\(G=<A,B,E>\)。很显然,求解是否存在一种使得所有不回家的本校学生和来看他们的校外人都有地方住,就相当于求此二部图的最大匹配边数能否满足所需床位数。采用dfs+匈牙利算法求解。

代码

#include <bits/stdc++.h>
using namespace std;
int T, n;
int Link[60][60] = {}, matched[60] = {}, isStudent[60] = {}, goesHome[60] = {};
int flgz;
bool visited[100] = {};

inline int readNum()
{
    int x = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9')
        ch = getchar();
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x;
}

bool dfs(int x)
{
    for (int j = 1; j <= Link[x][0]; j++)
    {
        //寻找第x个人可连接的边,这样的边一共有Link[x][0]条
        if (!visited[Link[x][j]] && isStudent[Link[x][j]])
        { //!visited的含义是:对于第x个人,他连接的第j条边没有被匹配过
            visited[Link[x][j]] = true;
            if (!matched[Link[x][j]] || dfs(matched[Link[x][j]]))
            { //如果第j床没有被匹配,或向下搜索找到增广交错路径
                matched[Link[x][j]] = x;
                //就将x的第j条边连接的床标号为x
                return true;
            }
        }
    }
    return false;
}

int main()
{
    T = readNum();
    while (T--)
    {
        int rbq = 0;
        memset(Link, 0, sizeof(Link));
        memset(matched, 0, sizeof(p));
        memset(isStudent, 0, sizeof(isStudent));
        memset(goesHome, 0, sizeof(goesHome));
        n = readNum();
        for (int i = 1; i <= n; i++)
            isStudent[i] = readNum();
        for (int i = 1; i <= n; i++)
        {
            rbq = readNum();
            if (isStudent[i])
                goesHome[i] = rbq;
            else
                goesHome[i] = -1;
        }
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                rbq = readNum();
                if (goesHome[i] == 1)
                    continue;
                if (rbq)
                { //如果i和j认识,i可以用j的床
                    Link[i][0]++;
                    //[0]表示第i个人当前连接的边数,下记作ki
                    Link[i][Link[i][0]] = j;
                    //如果ij认识,把i连接的第ki条边标记上j
                }
                if (i == j && isStudent[i] == 1)
                {
                    Link[i][0]++;
                    Link[i][Link[i][0]] = j;
                    //如果第i个人是学生,将其和自己的床连一条边
                }
            }
        }
        flgz = 1;
        for (int i = 1; i <= n; i++)
        {
            if (goesHome[i] != 1)
            {
                memset(visited, 0, sizeof(visited));
                if (!dfs(i))
                {
                    printf("T_T\n");
                    flgz = 0;
                    break;
                }
            }
        }
        if (flgz)
            printf("^_^\n");
    }
}
posted @ 2020-04-14 15:09  Allegro_VivAce  阅读(338)  评论(0编辑  收藏  举报