洛谷Oj- P2055 [ZJOI2009]假期的宿舍-二分圖最大匹配
題目描述:
學校放假了 · · · · · · 有些同學回家了,而有些同學則有以前的好朋友來探訪,那麼住宿就是一個問題。比如 A 和 B 都是學校的學生,A 要回家,而 C 來看B,C 與 A 不認識。我們假設每個人只能睡和自己直接認識的人的床。那麼一個解決方案就是 B 睡 A 的床而 C 睡 B 的床。而實際情況可能非常複雜,有的人可能認識好多在校學生,在校學生之間也不一定都互相認識。我們已知一共有 n 個人,並且知道其中每個人是不是本校學生,也知道每個本校學生是否回家。問是否存在一個方案使得所有不回家的本校學生和來看他們的其他人都有地方住。
AC程式碼(匈牙利演算法):
int n;
bool s[60],h[60],r[60][60],g[60][60];
bool book[60];//標記,是否被搜過
int match[60];//記錄匹配
bool dfs(int x)//以頂點x為起點,尋找增廣路
{
for(int i = 1; i <= n; ++i)//對於每一個頂點
{
if(g[x][i] == true && book[i] == false)//如果頂點x與頂點i有邊且頂點i沒被標記
{
book[i] = true;//標記
if(match[i] == 0 || dfs(match[i]) == true )//如果頂點i沒被匹配,或者與頂點i匹配的頂點找到了新歡
{
match[i] = x;//頂點i與頂點x匹配
return true;//成功找到了增廣路
}
}
}
return false;//沒找到
}
int Hungarian()//匈牙利演算法
{
int sum = 0;
memset(match,0,sizeof(match));//重置
for(int i = 1; i <= n; ++i)//每個需要進行匹配頂點都要去找增廣路
{
//需要匹配的是那些要麼是來訪者,要麼是不回家的在校學生
if(s[i] == false || (s[i] == true && h[i] == false))
{
memset(book,0,sizeof(book));//重置
if(dfs(i) == true)//如果找到了一條增廣路
sum++;//匹配數+1
}
}
return sum;
}
void create_graph()
{
for(int i = 1; i <= n; ++i)
if(s[i] == true && h[i] == false)
g[i][i] = 1;//那些不回家的在校學生可以睡自己的床
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
if(r[i][j] == true && s[j] == true)//如果i認識j且j是在校學生
g[i][j] = 1;//則i可以睡j的床
return;
}
int main()
{
int _;
cin >> _;//_組資料
while(_--)
{
cin >> n;
memset(g,0,sizeof(g));//重置
for(int i = 1; i <= n; ++i)
cin >> s[i];//是否是學生,不需要重置
for(int i = 1; i <= n; ++i)
cin >> h[i];//是否回家,不需要重置
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
cin >> r[i][j];//關係矩陣,不需要重置
int cnt = 0;
for(int i = 1; i <= n; ++i)//計算需要的匹配數
if(s[i] == false || (s[i] == true && h[i] == false))
cnt++;
create_graph();//建圖
if(Hungarian() == cnt)//跑演算法,如果能夠恰好匹配
puts("^_^");
else
puts("T_T");
}
return 0;
}
無故WA掉的程式碼:
int n;
bool s[60],h[60],r[60][60],g[60][60];
bool book[60];//二分圖匹配用標記陣列
int match[60];//記錄匹配的陣列
bool mark[60];//建圖用標記陣列
bool dfs(int x)
{
for(int i = 1; i <= n; ++i)
{
if(g[x][i] == true && book[i] == false)
{
book[i] = true;
if(match[i] == 0 || dfs(match[i]) == true)
{
match[i] = x;
return true;
}
}
}
return false;
}
int Hungarian()//匈牙利演算法
{
int sum = 0;
memset(match,0,sizeof(match));
for(int i = 1; i <= n; ++i)
{
//需要床的人:來訪者
if(s[i] == false)
{
memset(book,0,sizeof(book));
if(dfs(i) == true)
sum++;
}
}
return sum;
}
void dfs(int x,int y)//x是來訪者,y是在校學生,x認識y
{
if(h[y] == true)//如果y要回家
{
g[x][y] = 1;//x可以睡y的床
return;
}
for(int i = 1; i <= n; ++i)
{
//如果i是學生且y認識i且主角x與i的朋友關係沒被利用過
if(s[i] == true && r[y][i] == true && mark[i] == false)
{
mark[i] = true;//利用
dfs(x,i);//x便認識i了,搜尋
mark[i] = false;//取消標記,去尋找別的關係
}
}
return;
}
int main()
{
int _;
cin >> _;//_組資料
while(_--)
{
cin >> n;
memset(g,0,sizeof(g));//初始化
for(int i = 1; i <= n; ++i)
cin >> s[i];//是否是學生,不需要重置
for(int i = 1; i <= n; ++i)
cin >> h[i];//是否回家,不需要重置
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
cin >> r[i][j];//關係矩陣,不需要重置
int cnt = 0;
for(int i = 1; i <= n; ++i)//計算需要床的人數
if(s[i] == false)//來訪者需要床
cnt++;
for(int i = 1; i <= n; ++i)
{
if(s[i] == false)//如果i為來訪者
{
for(int j = 1; j <= n; ++j)
{
//j為在校學生且i認識j
if(s[j] == true && r[i][j] == true)
{
memset(mark,0,sizeof(mark));//重置
mark[j] = true;//因為與j的朋友關係被利用過,故標記
dfs(i,j);//搜尋
mark[j] = false;//恢復
}
}
}
}
if(Hungarian() >= cnt)
puts("^_^");
else
puts("T_T");
}
return 0;
}
解決方法:
先說一下程式碼①
關鍵是建圖啊建圖
需要做的是將人和床匹配
什麼樣的人需要床呢,一是不回家的在校學生,二是來訪者
什麼情況下人能睡床呢,一:在校學生自己能睡自己的床,二:任何人,只要認識在校學生,就能睡對方的床
不用考慮床位空不出來的情況(來訪者認識在校生A,A卻不認識任何人)嗎?這個問法太現實了,不夠抽象。什麼叫空不空出來。現在人和床沒有任何關係,只不過是頂點和頂點的關係。鳩佔鵲巢也罷,我們不關心。(實際情況肯定是來訪者沒床睡,但在演算法中,有可能是來訪者睡了A的床)
關於匈牙利的演算法的更好的解釋請參考《啊哈!演算法》
另本題還可以用網路流來求二分圖最大匹配
再說說我的程式碼:
按照題意,很容易想到傳遞性,A是來訪者,認識在校生B,在校生B又認識在校生C,故A睡B的床,B睡C的床。
為何不乾脆讓A睡C的床呢,難道不是等價的嗎
於是我就根據傳遞性,用深搜來建圖,根據關係鏈,將來訪者向能到達的回家的在校生連邊
只需要來訪者和離校生的床做匹配,在校生睡自己的床去
可是WA兩個點,資料還不讓下載……想不明白