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

題目描述

學校放假了.....有些同窗回家了,而有些同窗則有之前的好朋友來探訪,那麼住宿就是一個問題。好比A和B都是學校的學生,A要回家,而C來看B,C與A不認識。咱們假設每一個人只能睡♂和本身直接認識的人(的牀)。那麼一個解決方案就是B睡A(的牀)而C睡B(的牀)。而實際狀況可能很是複雜,有的人可能認識好多在校學生,在校學生之間也不必定都互相認識。咱們已知一共有\(n\)我的,而且知道其中每一個人是否是本校學生,也知道每一個本校學生是否回家。問是否存在一個方案使得全部不回家的本校學生和來看他們的其餘人都有地方住。c++

輸入輸出格式

輸入

第一行一個數\(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)。spa

輸入輸出樣例

輸入樣例
1
3
1 1 0
0 1 0
0 1 1
1 0 0
1 0 0
輸出樣例
^_^

數據範圍

對於\(30\%\)的數據知足\(1 \leq n \leq 12\).code

對於\(100\%\)的數據知足\(1 \leq n \leq 50,1\le T\le 20\).get

思路

這個問題中咱們把\(n\)我的看做集合\(A\),把\(n\)張牀看做集合\(B\)(但顯然非本校學生沒有牀位)。若是對第\(i\)我的和第\(j\)張牀,它們之間知足如下關係:it

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

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");
    }
}
相關文章
相關標籤/搜索