传送门:BZOJ1059
有趣的题目。
做法是二分图完美匹配,其实看到题就可以隐约猜到是把行和列连边。
建图方法是把黑点(i,j)的行i向列j连一条边,这显然是一个二分图模型,判断有无完美匹配即可。
看上去很符合直觉,下面我来展示它的严谨证明。
在证明这个算法的正确性前,首先证明一个引理[1]:设n阶方阵中有n个点,它们互不在同一列也互不在同一列,则可用这n个点构造出n阶的目标矩阵。
首先断言:任意两个同一列的1,经历任意次平凡列变换或平凡行变换后,它们仍在同一列,这是显然的。于是类似的,任意两个同一行的1也具有此性质。[命题「1」]
那么可以立即推得:对于主对角线上两同为1元素(i,i)和(j,j),它们在一开始的矩阵中必然不在同一行或同一列。
现在我们可以给出对引理的证明了。
证明是归纳的:对于n=1,它是显然的;下面设命题对于n成立,证明命题对于n+1也成立。
我们取在第n+1列的点i,删去该点所在的列和行,现在我们得到了一个n阶方阵,由归纳假设,我们可以以其构造一个n阶的目标矩阵,现在将点i所在的列和行按变换前n+1阶方阵中的位置加入变换后的n阶方阵。
观察这个方阵,我们发现该方阵总是有这样的形式:
1 0 0 0……
0 1 0 0……
0 0 1 0……
………….
………….
0…1..0…..
0 ……0 0 1(k,n+1)
0…..1 0 …
0…… 1 . .
0…….0 1 0
现在我们注意到构造矩阵仅需要将[k,n+1]部分对角化,显然,它是满足归纳假设的。
于是我们可以将该矩阵对角化。
证毕。
接下来我们证明:原问题有解的充分必要条件是构造出的二分图具有完美匹配。
先证充分性,设图G有完美匹配,则显然可以选出n个点,让这n个点互不在同一行和列,于是根据引理1,命题得证。
再证必要性,设图G没有完美匹配,即是说不可以选出n个点使得各点互不在同一行和列,于是至少有两个点的行相同,于是根据命题「1」,至少两个点会在同一行,也就是说至多只有n-1个点能落在主对角线上,故不能构成题目要求的n阶矩阵,命题得证。
证毕。
于是我们只要求出原图是否具有完美匹配,就能解决这个问题。
这种数据范围就不上Dinic了,直接匈牙利吧。
我才不会说匈牙利写错调了一个小时的那个人是谁呢哼!
代码上的小细节见下。
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
int n,T;
bool used[205];
bool G[205][205];
int P[205];
bool find(int x)
{
for(int i=1;i<=n;i++)
if(G[x][i]&&!used[i]){
used[i]=true;
if(P[i]==-1||find(P[i])){
P[i]=x;
return true;
}
}
return false;
}
bool Judge()
{
for(int i=1;i<=n;i++){
memset(used,false,sizeof(used));
if(!find(i))
return false;
}
return true;
}
void MakeGraph()
{
memset(G,false,sizeof(G));
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
int a;
scanf("%d",&a);
if(a)
G[i][j]=true;
}
memset(P,-1,sizeof(P));
if(Judge())
puts("Yes");
else
puts("No");
}
void Readdata()
{
freopen("loli.in","r",stdin);
scanf("%d",&T);
}
void Close()
{
fclose(stdin);
fclose(stdout);
}
int main()
{
Readdata();
while(T--)
MakeGraph();
Close();
return 0;
}