题意:给一个n * n的矩阵,矩阵每个位置的值是0或1,问能否通过多次交换两行或者两列得到一个主对角线都是1的矩阵?如果能,输出交换次数和交换方案。
这题可以用二分图来搞。通过线性代数可知,只交换行或列就行了。考虑只交换行,可以用二分图建模。每一行对于列为1的列编号(设为i),表示这一行可以放在第i行,设这一行是第a行,可以建一条 a -> i 的边。观察一下发现输入的矩阵刚好就是我们要建的二分图,就省去了建图的过程。
为什么这样建图可行呢?每一行最终情况一定是放到了某一个位置,这是一个的一对一的匹配模型,因为一个位置只能放一行。按前面所说建图跑出最大匹配后,最大匹配数就是我们要的矩阵(主对角线为1的矩阵)的满足条件的行数(在i == j 的位置为1)。 如果最大匹配数为n,说明可以通过交换行得到这个矩阵。
如何输出交换方案? 注意到我们建图跑出匹配后,每一行都有一个匹配位置,设为nxt[i] , 且不重复,这一行必然要和nxt[i]行做一次交换的,把行编号展开成线性,把行号和匹配位置连起来观察一下发现,就是一个个不相交的环,是前面遇到过的模型(前面有博客)。
那么最小交换次数就是不相交的环数内点的个数减1之和,直接用数组遍历这个环模拟即可。详见代码
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
const int maxn = 110;
int n;
int mat[maxn][maxn],link[maxn][maxn],used[maxn],nxt[maxn];
int pos[maxn];
int vis[maxn];
bool find(int u){
for(int i = 1; i <= n; i++){
if(link[u][i] && !used[i]){
used[i] = 1;
if(!nxt[i] || find(nxt[i])){
nxt[i] = u;
return true;
}
}
}
return false;
}
int match(){
int res = 0;
for(int i = 1; i <= n; i++){
memset(used,0,sizeof(used));
if(find(i)) res++;
}
return res;
}
vector <pii> g;
int main(){
while(~scanf("%d",&n)){
memset(vis,0,sizeof(vis));
memset(nxt,0,sizeof(nxt));
g.clear();
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++)
cin >> link[i][j];
}
if(match() == n){
int cnt = 0;
memset(vis,0,sizeof(vis));
for(int i = 1; i <= n; i++){
int p = i;
if(vis[p]) continue;
vis[p] = 1;
while(!vis[nxt[p]]){
g.push_back(pii(p,nxt[p])); //一条边代表交换一次
vis[nxt[p]] = 1;
p = nxt[p];
}
}
printf("%d\n",g.size());
for(int i = 0; i < g.size(); i++)
printf("R %d %d\n",g[i].first,g[i].second);
}
else
cout << -1 << endl;
}
return 0;
}