题目大意:有两台机器,分别有m,n个状态,最开始的时候都位于状态0,现在给定了k个任务,每个任务都可以在A的某一个状态或B的某一个状态运行,求问完成所有的任务所需的切换机器状态的总次数
机器的每一个状态用一个点表示,显然这是一个二分图,如果在每个任务指定的两个状态连一条线的话就可以用一条边代表这个任务,而让这个任务在某台机器上执行就是给这条边选择一个端点,每切换一次状态就是选择一个点,那么这道题就转化为了:求最少的顶点,使边集中的每一条边都与某个顶点关联,即求二分图的最小点覆盖
再由Konig定理,二分图的最小点覆盖数=最大匹配数,那么用匈牙利算法就可以解决了~
(顺便查了一下才知道原来Konig就是柯尼希啊~不知道这个定理和物理学的质点动能那个定理有啥关系没......)
关于匈牙利算法:
算法思想很简单,不断的找增广路,然后把路上的边取反(实际操作上改一下匹配的对应关系就可以了),直到找不到为止,实现起来也很容易
另外对于这道题,因为最开始两台机器的状态都是0,所以与0相连的边是不需要转换状态的,这些边当成不存在就好了
下面是具体的代码
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <ctype.h>
#define MAXN 300
int a[MAXN][MAXN]; // 邻接矩阵
int match[MAXN]; // 标记Y集中的顶点匹配的对象,未匹配为-1
int vis[MAXN]; // 在某一次访问的过程中Y集中的该点是否被访问
int nx, ny; // xy集元素的个数
int total; // 最大匹配数目
// 从X集中的v点出发,寻找一条增广路
// 如果能找到返回true
bool find(int v)
{
// 枚举每一个y集中的点
for (int i = 0; i < ny; ++i)
{
// 若这两点间存在边而且i点尚未访问
if (a[v][i] && !vis[i])
{
vis[i] = 1;
// 递归终点为match[i] = -1,即增广路的另一个端点
// 若该点有匹配,查看是否存在从对应匹配点出发的增广路
if (match[i] == -1 || find(match[i]))
{
match[i] = v; // 修改匹配对象
return true;
}
}
}
return false;
}
// 读入整数
inline void scan(int &x)
{
char c;
while ((c = getchar()) && !isdigit(c));
x = c - '0';
while ((c = getchar()) && isdigit(c))
{
x = x * 10 + c - '0';
}
}
// 图初始化
void init()
{
int k;
total = 0;
memset(match, -1, sizeof(match));
memset(a, 0, sizeof(a));
scan(k);
for (int i = 0; i < k; ++i)
{
int t, b, c;
scan(t); scan(b); scan(c);
if (b == 0 || c == 0)
continue;
a[b][c] = 1;
}
}
int main()
{
int t;
while (1)
{
scan(nx);
if (nx == 0)
break;
scan(ny);
init();
for (int i = 0; i < nx; ++i)
{
// 枚举每一个起点,重置访问标记
// 若找到增广路则长度+1
memset(vis, 0, sizeof(vis));
if (find(i))
{
++total;
}
}
/*for (int i = 0; i < ny; ++i)
{
printf("%d: %d\n", i, match[i]);
}*/
printf("%d\n", total);
}
return 0;
}