二分图指的是这样一种图,其所有顶点可以分成两个集合X和Y,其中X或Y中任意两个在同一集合中的点都不相连,所有的边关联在两个顶点中,恰好一个属于集合X,另一个属于集合Y。给定一个二分图G,M为G边集的一个子集,如果M满足当中的任意两条边都不依附于同一个顶点,则称M是一个匹配。图中包含边数最多的匹配称为图的最大匹配。
二分图的最大匹配有两种求法,第一种是最大流;第二种就是我现在要讲的匈牙利算法。这个算法说白了就是最大流的算法,但是它跟据二分图匹配这个问题的特点,把最大流算法做了简化,提高了效率。
增广路径的定义(也称增广轨或交错轨):
若P是图G中一条连通两个未匹配顶点的路径,并且属M的边和不属M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径。
由增广路径的定义可以推出下述4个结论:
1-P的路径长度必定为奇数,第一条边和最后一条边都不属于M。
2-P上所有第奇数条边都不在M中,所有第偶数条边都出现在M中。
3-P经过取反操作可以得到一个更大的匹配M’。所谓“取反”即把P上所有第奇数条边(原不在M中)加入到M中,并把P中所有第偶数条边(原在M中)从M中删除,则新的匹配数就比原匹配数多了1个。(增广路顾名思义就是使匹配数增多的路径)
4-M为G的最大匹配当且仅当不存在相对于M的增广路径。
最大流算法的核心问题就是找增广路径(augment path)。匈牙利算法也不例外,它的基本模式就是:
初始时最大匹配为空
while 找得到增广路径
do 把增广路径加入到最大匹配中去
可见和最大流算法是一样的。但是这里的增广路径就有它一定的特殊性。(注:匈牙利算法虽然根本上是最大流算法,但是它不需要建网络模型,所以图中不再需要源点和汇点,仅仅是一个二分图。每条边也不需要有方向。)
算法的思路是不停的找增广路径, 并增加匹配的个数,增广路径顾名思义是指一条可以使匹配数变多的路径,在匹配问题中,增广路径的表现形式是一条"交错路径",也就是说这条由图的边组成的路径, 它的第一条边是目前还没有参与匹配的,第二条边参与了匹配,第三条边没有..最后一条边没有参与匹配,并且始点和终点还没有被选择过。这样交错进行,显然他有奇数条边。那么对于这样一条路径,我们可以将第一条边改为已匹配,第二条边改为未匹配...以此类推。也就是将所有的边进行"反色",容易发现这样修改以后,匹配仍然是合法的,但是匹配数增加了一对。另外,单独的一条连接两个未匹配点的边显然也是交错路径。可以证明。当不能再找到增广路径时,就得到了一个最大匹配,这也就是匈牙利算法的思路。
3个重要结论:
最小点覆盖数: 最小覆盖要求用最少的点(X集合或Y集合的都行)让每条边都至少和其中一个点关联。可以证明:最少的点(即覆盖数)=最大匹配数
最小路径覆盖=最小路径覆盖=|N|-最大匹配数
用尽量少的不相交简单路径覆盖有向无环图G的所有结点。解决此类问题可以建立一个二分图模型。把所有顶点i拆成两个:X结点集中的i和Y结点集中的i',如果有边i->j,则在二分图中引入边i->j',设二分图最大匹配为m,则结果就是n-m。
二分图最大独立集=顶点数-二分图最大匹配
在N个点的图G中选出m个点,使这m个点两两之间没有边,求m最大值。
如果图G满足二分图条件,则可以用二分图匹配来做.最大独立集点数 = N - 最大匹配数。
例题:
http://poj.org/problem?id=1469 COURSES
有了匈牙利算法的基础,该题就是一道非常简单的题目了:该题给出P门课程,N个学生,问能否从中选出P个学生,使每个学生上不同的课,且每个课程有一个学生。典型的二分图匹配的问题,我们只要计算最大二分图匹配数,如果和课程数相同就输出YES,否则输出NO。
//poj_1469
/*==================================================*\
| 二分图匹配(匈牙利算法DFS 实现)
| INIT: g[][]邻接矩阵;
| 优点:实现简洁容易理解,适用于稠密图,DFS找增广路快。
| 找一条增广路的复杂度为O(E),最多找V条增广路,故时间复杂度为O(VE)
==================================================*/
#include
#include
bool g[110][310]; //邻接矩阵,true代表有边相连
bool flag,visit[310]; //记录V2中的某个点是否被搜索过
int match[310]; //记录与V2中的点匹配的点的编号
int p,n; //二分图中左边、右边集合中顶点的数目
// 匈牙利算法
bool dfs(int u)
{
for (int i = 1; i <= n; ++i)
{
if (g[u][i] && !visit[i]) //如果节点i与u相邻并且未被查找过
{
visit[i] = true; //标记i为已查找过
if (match[i] == -1 || dfs(match[i])) //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径
{
match[i] = u; //记录查找成功记录,更新匹配M(即“取反”)
return true; //返回查找成功
}
}
}
return false;
}
int main(void)
{
int i,j,k,t,v,ans;
scanf("%d",&t);
while (t--)
{
scanf("%d %d", &p, &n);
for (i = 1; i <= p; i++)
{
for (j = 1; j <= n; j++)
g[i][j] = false;
}
for (i = 1; i <= n; i++)
match[i] = -1;
flag = true;
for (i = 1; i <= p; i++)
{
scanf("%d",&k);
if (k == 0)
flag = false;
while (k--)
{
scanf("%d",&v);
g[i][v] = true;
}
}
if (flag)
{
ans = 0;
for (i = 1; i <= p; i++)
{
memset(visit,false,sizeof(visit)); //清空上次搜索时的标记
if( dfs(i) ) //从节点i尝试扩展
ans++;
}
if (ans == p)
puts("YES");
else
puts("NO");
}
else
puts("NO");
}
return 0;
}
pku 2446 二分图最大匹配的应用
http://poj.org/problem?id=2446 Chessboard
题意:给出一个矩形N*M棋盘,有K个格子是空洞,然后用2*1的矩形,对所有非空洞的格子进行覆盖,如果可以全部覆盖,就puts("YES");
算法:建立二分图,用匈牙利算法;
我们分别对所有的格子进行标号1.。。N*M
将问题转化为二分图最大匹配问题。将棋盘按国际象棋棋盘那样添上黑白两种颜色,这样的话,黑色和白色的格子就构成了二分图的两个集合,即相邻的两个格子不会属于同个集合的。然后从上到下,从左到右对格子进行编号(除了洞),相邻的两格用边相连就构成一个二分图。然后求出最大匹配。。如果最大匹配+K=N*M就输出YES。。
//poj_2446
/*==================================================*\
| 二分图匹配(匈牙利算法DFS 实现)
| INIT: g[][]邻接矩阵;
| 优点:实现简洁容易理解,适用于稠密图,DFS找增广路快。
| 找一条增广路的复杂度为O(E),最多找V条增广路,故时间复杂度为O(VE)
==================================================*/
#include
#include
#define MAX 1089 //33*33
bool g[MAX][MAX]; //邻接矩阵,true代表有边相连
bool flag,visit[MAX]; //记录V2中的某个点是否被搜索过
int match[MAX]; //记录与V2中的点匹配的点的编号
int cnt; //二分图中左边、右边集合中顶点的数目
bool hole[MAX][MAX];
int id[MAX][MAX];
// 匈牙利算法
bool dfs(int u)
{
for (int i = 1; i <= cnt; ++i)
{
if (g[u][i] && !visit[i]) //如果节点i与u相邻并且未被查找过
{
visit[i] = true; //标记i为已查找过
if (match[i] == -1 || dfs(match[i])) //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径
{
match[i] = u; //记录查找成功记录,更新匹配M(即“取反”)
return true; //返回查找成功
}
}
}
return false;
}
int MaxMatch()
{
int i,sum=0;
memset(match,-1,sizeof(match));
for(i = 1 ; i <= cnt ; ++i)
{
memset(visit,false,sizeof(visit)); //清空上次搜索时的标记
if( dfs(i) ) //从节点i尝试扩展
{
sum++;
}
}
return sum;
}
int main(void)
{
int i,j,k,m,n,ans,y,x;
while (scanf("%d %d %d",&m,&n,&k)!=EOF)
{
memset(g,false,sizeof(g));
memset(hole,false,sizeof(hole));
for (i = 1; i <= k; ++i)
{
scanf("%d %d",&y,&x);
hole[x][y] = true;
}
if((m*n-k)&1) //奇偶剪枝
{
puts("NO");
continue;
}
cnt = 0;
for (i = 1; i <= m; ++i)
{
for (j = 1; j <= n; ++j)
{
if(hole[i][j] == false) //对没有涂黑的点进行标号
{
id[i][j] = ++cnt;
}
}
}
for (i = 1; i <= m; ++i)
{
for (j = 1; j <= n; ++j)
{
if(hole[i][j] == false)
{
if(i-1>0 && hole[i-1][j] == false) //建图。。要注意边界问题
g[ id[i][j] ][ id[i-1][j] ] = true;
if(i+1<=m && hole[i+1][j] == false)
g[ id[i][j] ][ id[i+1][j] ] = true;
if(j-1>0 && hole[i][j-1] == false)
g[ id[i][j] ][ id[i][j-1] ] = true;
if(j+1<=n && hole[i][j+1] == false)
g[ id[i][j] ][ id[i][j+1] ] = true;
}
}
}
ans = MaxMatch();
if (ans == cnt)
puts("YES");
else
puts("NO");
}
return 0;
}
静态邻接表模板:
//poj_2446
/*==================================================*\
| 二分图匹配(匈牙利算法DFS 实现)
| 邻接表方法来实现;
| 优点:实现简洁容易理解,适用于稠密图,DFS找增广路快。
| 找一条增广路的复杂度为O(E),最多找V条增广路,故时间复杂度为O(VE)
==================================================*/
#include
#include
#define MAX 1089 //33*33
bool flag,visit[MAX]; //记录V2中的某个点是否被搜索过
int match[MAX]; //记录与V2中的点匹配的点的编号
int cnt; //二分图中左边、右边集合中顶点的数目
bool hole[MAX][MAX];
int id[MAX][MAX];
int head[MAX];
struct edge
{
int to,next;
}e[100005];
int index;
void addedge(int u,int v)
{ //向图中加边的算法,注意加上的是有向边
//u为v的后续节点既是v---->u
e[index].to=v;
e[index].next=head[u];
head[u]=index;
index++;
}
// 匈牙利(邻接表)算法
bool dfs(int u)
{
int i,v;
for(i = head[u]; i != 0; i = e[i].next)
{
v = e[i].to;
if(!visit[v]) //如果节点v与u相邻并且未被查找过
{
visit[v] = true; //标记v为已查找过
if(match[v] == -1 || dfs(match[v])) //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径
{
match[v] = u; //记录查找成功记录,更新匹配M(即“取反”)
return true; //返回查找成功
}
}
}
return false;
}
int MaxMatch()
{
int i,sum=0;
memset(match,-1,sizeof(match));
for(i = 1 ; i <= cnt ; ++i)
{
memset(visit,false,sizeof(visit)); //清空上次搜索时的标记
if( dfs(i) ) //从节点i尝试扩展
{
sum++;
}
}
return sum;
}
int main(void)
{
int i,j,k,m,n,ans,y,x;
while (scanf("%d %d %d",&m,&n,&k)!=EOF)
{
memset(hole,false,sizeof(hole));
for (i = 1; i <= k; ++i)
{
scanf("%d %d",&y,&x);
hole[x][y] = true;
}
if((m*n-k)&1) //奇偶剪枝
{
puts("NO");
continue;
}
cnt = 0;
index = 1;
for (i = 1; i <= m; ++i)
{
for (j = 1; j <= n; ++j)
{
if(hole[i][j] == false) //对没有涂黑的点进行标号
{
id[i][j] = ++cnt;
}
}
}
memset(head,0,sizeof(head)); //切记要初始化
for (i = 1; i <= m; ++i)
{
for (j = 1; j <= n; ++j)
{
if(hole[i][j] == false)
{
if(i-1>0 && hole[i-1][j] == false) //建图。。要注意边界问题
addedge(id[i][j],id[i-1][j]);
if(i+1<=m && hole[i+1][j] == false)
addedge(id[i][j],id[i+1][j]);
if(j-1>0 && hole[i][j-1] == false)
addedge(id[i][j],id[i][j-1]);
if(j+1<=n && hole[i][j+1] == false)
addedge(id[i][j],id[i][j+1]);
}
}
}
ans = MaxMatch();
if (ans == cnt)
puts("YES");
else
puts("NO");
}
return 0;
}
http://poj.org/problem?id=1274
给出奶牛们的爱好的信息,计算最大分配方案。
#include
#include
#define MAX 202
bool flag,visit[MAX]; //记录V2中的某个点是否被搜索过
int match[MAX]; //记录与V2中的点匹配的点的编号
int cow, stall; //二分图中左边、右边集合中顶点的数目
int head[MAX];
struct edge
{
int to,next;
}e[3000];
int index;
void addedge(int u,int v)
{ //向图中加边的算法,注意加上的是有向边
//u为v的后续节点既是v---->u
e[index].to=v;
e[index].next=head[u];
head[u]=index;
index++;
}
// 匈牙利(邻接表)算法
bool dfs(int u)
{
int i,v;
for(i = head[u]; i != 0; i = e[i].next)
{
v = e[i].to;
if(!visit[v]) //如果节点v与u相邻并且未被查找过
{
visit[v] = true; //标记v为已查找过
if(match[v] == -1 || dfs(match[v])) //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径
{
match[v] = u; //记录查找成功记录,更新匹配M(即“取反”)
return true; //返回查找成功
}
}
}
return false;
}
int MaxMatch()
{
int i,sum=0;
memset(match,-1,sizeof(match));
for(i = 1 ; i <= cow ; ++i)
{
memset(visit,false,sizeof(visit)); //清空上次搜索时的标记
if( dfs(i) ) //从节点i尝试扩展
{
sum++;
}
}
return sum;
}
int main(void)
{
int i,j,k,ans,m;
while (scanf("%d %d",&cow, &stall)!=EOF)
{
memset(head,0,sizeof(head)); //切记要初始化
index = 1;
for (i = 1; i <= cow; ++i)
{
scanf("%d",&k);
for (j = 0; j < k; ++j)
{
scanf("%d",&m);
addedge(i , m);
}
}
ans = MaxMatch();
printf("%d\n",ans);
}
return 0;
}
http://poj.org/problem?id=1325
分析:显然,机器重启次数是两台机器需要使用的不同模式的个数。把每个任务看成一条边,即A机器的每个模式看成一个X节点,B机器的每个模式看成一个Y节点,任务i为边(ai, bi)。本题即求最少的点让每条边至少与其中的一点关联,即求一个点的最小覆盖。可以证明,这个最小覆盖就是该二分图的最大匹配数。故二分图匹配的模型就建好了。注意到开始时机器都处于0模式,所以如果某个任务可以在0模式下执行,则我们可以不考虑该任务,假定它已经被完成即可,也就是建图的时候不要把与0关联的边加到二分图中就可以得到正确的解。
#include
#include
#define MAX 105
bool visit[MAX]; //记录V2中的某个点是否被搜索过
int match[MAX]; //记录与V2中的点匹配的点的编号
int n,m; //二分图中左边、右边集合中顶点的数目
int head[MAX];
struct edge
{
int to,next;
}e[1005];
int index;
void addedge(int u,int v)
{ //向图中加边的算法,注意加上的是有向边
//u为v的后续节点既是v---->u
e[index].to=v;
e[index].next=head[u];
head[u]=index;
index++;
}
// 匈牙利(邻接表)算法
bool dfs(int u)
{
int i,v;
for(i = head[u]; i != 0; i = e[i].next)
{
v = e[i].to;
if(!visit[v]) //如果节点v与u相邻并且未被查找过
{
visit[v] = true; //标记v为已查找过
if(match[v] == -1 || dfs(match[v])) //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径
{
match[v] = u; //记录查找成功记录,更新匹配M(即“取反”)
return true; //返回查找成功
}
}
}
return false;
}
int MaxMatch()
{
int i,sum=0;
memset(match,-1,sizeof(match));
for(i = 1 ; i < n ; ++i)
{
memset(visit,false,sizeof(visit)); //清空上次搜索时的标记
if( dfs(i) ) //从节点i尝试扩展
{
sum++;
}
}
return sum;
}
int main(void)
{
int i,j,k,ans,y,x;
while (scanf("%d",&n),n)
{
scanf("%d %d",&m,&k);
index = 1;
memset(head,0,sizeof(head)); //切记要初始化
for (i = 1; i <= k; ++i)
{
scanf("%d %d %d",&j,&x,&y);
if(x && y)
addedge(x,y);
}
ans = MaxMatch();
printf("%d\n",ans);
}
return 0;
}
动态邻接表模板:
#include
using namespace std;
int n,m,k;
bool visit[105]; //每次找增广路时对Y中点是否访问
int match[105]; //Y中点匹配的X中点的位置
struct edge
{
int from;
int to;
edge* next;
edge()
{
from = to = 0;
next = NULL;
}
};
edge* List[105];
void add_edge(int f,int t)
{
edge* node = new edge();
node->from = f;
node->to = t;
node->next = List[f];
List[f] = node;
}
// 匈牙利(邻接表)算法
bool dfs(edge* node)
{
int i;
for(; node != NULL; node = node->next)
{
i = node->to;
if(visit[i] == NULL) //如果节点v与u相邻并且未被查找过
{
visit[i] = true; //标记v为已查找过
if(match[i] == -1 || dfs( List[match[i]] ) ) //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径
{
match[i] = node->from; //记录查找成功记录,更新匹配M(即“取反”)
return true; //返回查找成功
}
}
}
return false;
}
int main(void)
{
int i,x,y;
while(scanf("%d",&n)!=EOF && n!=0)
{
for(i=0;i<=n;i++)//链表清空,一开始没清空,错了很多次
{
List[i] = NULL;
}
memset(match,-1,sizeof(match));
scanf("%d%d",&m,&k);
while(k--)
{
scanf("%d%d%d",&i,&x,&y);
if(x && y)
add_edge(x,y);
}
int sum = 0;
for(i=1;i<=n;i++)
{
memset(visit,false,sizeof(visit));
if( dfs(List[i]) )
sum++;
}
printf("%d\n",sum);
}
return 0;
}
http://acm.hdu.edu.cn/showproblem.php?pid=1281棋盘游戏
又是一种建图的方式,对于一个坐标,x , y 可以分成两个不同的集合,如果该点满足某种性质的话,就在 x , y 上连一条线,本题就是这样的……
本题要求关键点,那么只需要对于每个可行点进行删点,然后看看得出的最大匹配是否小于不删点的解,如果小于,则是关键点……统计一下即可。
代码:
/*==================================================*\
| 二分图匹配(匈牙利算法DFS 实现)
| INIT: g[][]邻接矩阵;
| 优点:实现简洁容易理解,适用于稠密图,DFS找增广路快。
| 找一条增广路的复杂度为O(E),最多找V条增广路,故时间复杂度为O(VE)
==================================================*/
#include
#include
bool g[101][101]; //邻接矩阵,true代表有边相连
bool visit[101]; //记录V2中的某个点是否被搜索过
int match[101]; //记录与V2中的点匹配的点的编号
int n,m,k; //二分图中左边、右边集合中顶点的数目
// 匈牙利算法
bool dfs(int u)
{
for (int i = 1; i <= m; ++i)
{
if (g[u][i] && !visit[i]) //如果节点i与u相邻并且未被查找过
{
visit[i] = true; //标记i为已查找过
if (match[i] == -1 || dfs(match[i])) //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径
{
match[i] = u; //记录查找成功记录,更新匹配M(即“取反”)
return true; //返回查找成功
}
}
}
return false;
}
inline int MaxMatch()
{
int i,sum=0;
memset(match,-1,sizeof(match));
for(i = 1 ; i <= n ; ++i)
{
memset(visit,false,sizeof(visit)); //清空上次搜索时的标记
if( dfs(i) ) //从节点i尝试扩展
{
sum++;
}
}
return sum;
}
int main(void)
{
int i,j,ans,x,y,num,t=1;
while (scanf("%d %d %d",&n,&m,&k)!=EOF)
{
memset(g,false,sizeof(g)); //初始化
for (i = 1; i <= k; ++i)
{
scanf("%d %d",&x,&y);
g[x][y] = true;
}
ans = MaxMatch();
num = 0;
for (i = 1; i <= n; ++i)
{
for (j = 1; j <= m; ++j)
{
if(g[i][j] == true)
{
g[i][j] = false;
if(MaxMatch() < ans)
num++;
g[i][j] = true;
}
}
}
printf("Board %d have %d important blanks for %d chessmen.\n",t++,num,ans);
}
return 0;
}
http://acm.hdu.edu.cn/showproblem.php?pid=2819Swap
题目的意思是,通过一系列交换,让矩阵中A[i, i] (1 <= i <= N)的值全为1。
首先要明确:如果某行或者某列全是0。那怎么换都没办法的。否则,一定能换出来。这个动动脑子想一下可以明白的。
其实就是简单的二分匹配,行和列匹配就可以了,关键是点不在于匹配而在于排序,因为匹配后的match存储的是列的匹配对象,所以只需要把列从小到大(或者从大到小,因为是special judge,所以主、副对角线都是一样)排序,每排序一次就保存当前交换了的下标,注意这里不能用冒泡而最好用选择,因为题目要求len不能大于1000,,这里纠结了一下,郁闷死了)
其次要明确:只交换行或者只交换列都是可以换出来的,这个动动脑子想一下也可以明白的。
明确了这两点,这问题就变成了二分图的匹配问题。
二分图左边的节点为每一行的行号,二分图右边的节点为每一行中出现的“1”对应的列号,这样用匈牙利算法就可以匹配了。
/*
最大二分匹配+选择排序
开始压根没想到会是二分图匹配问题,做题太少,只能说水平不够吧。
将行数放在左边,右边为连接该行为1的所在列数,再求二分图的最大匹配,若能完全匹配,则存在。
再用选择排序的方法,次算出行之间的交换次数,并保存结果。
*/
#include
#include
using namespace std;
#include
bool visit[101]; //记录V2中的某个点是否被搜索过
int match[101]; //记录与V2中的点匹配的点的编号
int n; //二分图中左边、右边集合中顶点的数目
bool flag;
int a[101],b[101],head[101];
struct edge
{
int to,next;
}e[5005];
int index;
void addedge(int u,int v)
{ //向图中加边的算法,注意加上的是有向边
//u为v的后续节点既是v---->u
e[index].to=v;
e[index].next=head[u];
head[u]=index;
index++;
}
// 匈牙利(邻接表)算法
bool dfs(int u)
{
int i,v;
for(i = head[u]; i != 0; i = e[i].next)
{
v = e[i].to;
if(!visit[v]) //如果节点v与u相邻并且未被查找过
{
visit[v] = true; //标记v为已查找过
if(match[v] == -1 || dfs(match[v])) //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径
{
match[v] = u; //记录查找成功记录,更新匹配M(即“取反”)
return true; //返回查找成功
}
}
}
return false;
}
int MaxMatch()
{
int i,sum=0;
memset(match,-1,sizeof(match));
for(i = 1 ; i <= n ; ++i)
{
memset(visit,false,sizeof(visit)); //清空上次搜索时的标记
if( dfs(i) ) //从节点i尝试扩展
{
sum++;
}
else
{
flag = false;
break;
}
}
return sum;
}
inline bool scan_d(int &num) // 这个就是 加速的 关键了
{
char in;bool IsN=false;
in=getchar();
if(in==EOF)
return false;
while(in!='-'&&(in<'0'||in>'9')) in=getchar();
if(in=='-') { IsN=true;num=0;}
else num=in-'0';
while(in=getchar(),in>='0'&&in<='9')
{
num*=10,num+=in-'0';
}
if(IsN)
num=-num;
return true;
}
int main(void)
{
int i,j,ans,k,cnt,value;
while (scanf("%d",&n)!=EOF)
{
index = 1;
flag = true;
memset(head,0,sizeof(head)); //切记要初始化,否则会超时的
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
{
scan_d(value);
if(value)
addedge(i,j);
}
}
ans = MaxMatch();
if(!flag)
puts("-1");
else
{
cnt = 0;
for (i = 1; i <= n; i++)
{
k = i;
for(j = i; j <= n; j++)
{
if(match[j] < match[k])
k = j;
}
if(k != i)
{
a[cnt]=i;
b[cnt++]=k;
swap(match[i] , match[k]);
}
}
printf("%d\n",cnt);
for (i = 0; i < cnt; i++)
printf("C %d %d\n",a[i],b[i]);
}
}
return 0;
}