题干:
Given an N*N matrix with each entry equal to 0 or 1. You can swap any two rows or any two columns. Can you find a way to make all the diagonal entries equal to 1?
Input
There are several test cases in the input. The first line of each test case is an integer N (1 <= N <= 100). Then N lines follow, each contains N numbers (0 or 1), separating by space, indicating the N*N matrix.
Output
For each test case, the first line contain the number of swaps M. Then M lines follow, whose format is “R a b” or “C a b”, indicating swapping the row a and row b, or swapping the column a and column b. (1 <= a, b <= N). Any correct answer will be accepted, but M should be more than 1000.
If it is impossible to make all the diagonal entries equal to 1, output only one one containing “-1”.
Sample Input
2
0 1
1 0
2
1 0
1 0
Sample Output
1
R 1 2
-1
解题报告:
看到数据量,猜到二分图。然后仔细分析二分图确实是可解的,正好二分图自带记录路径,,可能还是不是很熟练吧第一反应没有建模成二分图。左侧是行,右侧是列,然后跑二分图就可以了、题目没说求最短的变换次数,只是说一个specialjudge唯一要求是小于1000的变换次数。所以用二分图找到之后,那最多就是变换n次嘛(但是也有可能小于n次,比如第一个样例,就直接换了一次凑出俩来,严谨来讲(n/2)<=cnt<=n),n<=100,所以肯定满足题意啊。如果说求最少的变换次数,那就应该先把(ans[i]== j && ans[j] == i)的找出来,然后再遍历剩下的?(但是感觉好像和直接遍历的 次数差不多啊。。但是不太会证明、、)
AC代码:(140ms)
#include<bits/stdc++.h>
using namespace std;
const int MAX = 100 + 5;
bool used[MAX];
int line[MAX][MAX],nxt[MAX],ans[MAX],n,cnt;
bool find(int x) {
for(int i = 1; i<=n; i++) {
if(line[x][i] && used[i] == 0) {
used[i]=1;
if(nxt[i] == 0 || find(nxt[i])) {
nxt[i] = x;
return 1;
}
}
}
return 0;
}
int main()
{
while(~scanf("%d",&n)) {
cnt = 0;
memset(line,0,sizeof line);
memset(ans,0,sizeof ans);
memset(nxt,0,sizeof nxt);
for(int i = 1; i<=n; i++) {
for(int j = 1; j<=n; j++) {
scanf("%d",&line[i][j]);
}
}
for(int i = 1; i<=n; i++) {
memset(used,0,sizeof used);
if(find(i)) cnt++;
}
if(cnt != n) {
puts("-1");continue;
}
cnt = 0;
for(int i = 1; i<=n; i++) {
ans[nxt[i]] = i;//ans[i] = j 第i列应该等于第j列 //或者说 第i行换到第j行
}
for(int i = 1; i<=n; i++) {
if(ans[i] != i) {//如果这行需要换
for(int j = i+1; j<=n; j++) {
if(ans[j] == i) {
swap(ans[i],ans[j]);cnt++;
}
}
}
}
printf("%d\n",cnt);
for(int i = 1; i<=n; i++) {
ans[nxt[i]] = i;//ans[行] = 列
}
for(int i = 1; i<=n; i++) {
if(ans[i] != i) {
for(int j = i+1; j<=n; j++) {
if(ans[j] == i) {
printf("R %d %d\n",i,j);
swap(ans[i],ans[j]);
}
}
}
}
}
return 0 ;
}
总结:
for(int i = 1; i<=n; i++) {
ans[nxt[i]] = i;//ans[行] = 列
}
这一行作用是:ans[i]=j,表示第i行应该换到第j行。(你如果最后输出是C的,也就是换列的,那就不需要这个转换了)
还有一点值得注意,我们的思路不是 如果第i行需要换(即ans[i]=j),则把i和j交换,并且用一个bk数组标记第j行已经完成,再从1找第一个bk=0的行,然后再进行交换。(这样会使代码写起来复杂。而且每次都得从i=1开始找,因为有可能ans[i]=j其中j<i?不知道会不会有这种可能性)(但是时间复杂度是不会变的,因为每次都会有一个行被确定下来)
当然,也可以AC,下面是部分代码:(140ms)
for(int i = 1; i<=n; i++) {
ans[nxt[i]] = i;//ans[行] = 列
}
int flag = 0;
while(1) {
flag = 0;
for(int i = 1; i<=n; i++) {
if(ans[i] != i) {
flag = 1;
int j = ans[i];
printf("R %d %d\n",i,j);
swap(ans[i],ans[j]);
break;
}
}
if(flag == 0) break;
}
但是,我们不妨换个思路,扫到这行i之后,然后现在我们的想法是要把这样固定下来,也就是,从后面的行中找到一行假设为j,使得ans[j] = i , 也就是用第j行来确定第i行,然后这两行交换,这样一个for循环扫每一行就可以了,扫到一行,就从后面的行中找一行来确定这一行。就ok了。至于为什么从i+1行开始,是因为第1~(i-1)行,都已经是ans[i]=i了呀,这也就是这两个方法的不同。(前者是找到一个可以更新别的行的行,然后去更新后面的行;后者是枚举每一行,然后从后面找一行来更新这一行。不过代码实现起来都是if(ans[i] != i)这一句)