二分图
一个图的所有顶点可以分为两个集合X和Y
eg. 婚配问题:所有边一端在男,一端在女
最大匹配
匈牙利算法
基本思想
(想象男女婚配问题,男生在左,女生在右)先看左侧的男生,对于第一个男生,在右边查找他所能匹配的第一个女生,并且连接。如此往复。假如出现下面的情况:一个男生(记为男1)发现他所能匹配的第一个女生(记为女1)已经有匹配对象(记为男2)了,那么就以女1的匹配对象(男2)为研究对象,看男2除了当下的这个匹配对象以外,接下来还有没有可以匹配的对象,假如下一个女生(记为女2)也有了匹配对象,那么就递归下去(以女2的匹配对象(记为男3)为研究对象……),假如没有,那么就把他们连接起来。每一次dfs操作假如能够成功,那么匹配数就+1。
所以一共做N次dfs。
二分图的最小顶点覆盖
基本概念
求二分图中最少有几个点,使得每条边都和至少一个点关联。(用最少的点覆盖边)
基本思想
结果就和求解最大匹配数一样。因为当删除最大匹配的所有点之后,其他所有的点之间的关系都断掉了,那么就说明所有的边都和这些点有关系。
经典例题
机器任务安排
解:机器A的模式作为左边的点,机器B的模式作为右边的点。假如一个任务既可以在A上完成,又可以在B上完成,那么就在这两个对应的点当中画一条边。所以这个问题就等效于求解最小顶点覆盖问题。注意A、B最开始都处于0号模式。
#include <cstdio>
#include <cstring>
using namespace std;
int uNum, vNum, k, linker[100]; //link用来存右点的对象,下表表示第几个右点
bool graph[100][100], visit[100]; //邻接矩阵与标记数组(标记每一次搜索中右点是否访问过)
int hungary();
bool dps(int);
int main()
{
while(scanf("%d", &uNum), uNum)
{
int u, v, id;
scanf("%d%d", &vNum, &k);
memset(graph, false, sizeof(graph));
while(k--)
{
scanf("%d%d%d", &id, &u, &v);
if(u != 0 && v != 0) //不需要考虑需要机器A、B模式为0的情况
graph[u][v] = true;
}
printf("%d\n", hungary());
}
return 0;
}
int hungary()
{
int res = 0;
memset(linker, -1, sizeof(linker));
for(int u = 1; u < uNum; u++) //做n次dfs,dfs返回true就表明这一次可以找到匹配
{
memset(visit, false, sizeof(visit));
if(dps(u)) res++;
}
return res;
}
bool dps(int u)
{
for(int v = 1; v < vNum; v++)
if(graph[u][v] && !visit[v]) //如果有联系而且右点这一趟有没有考虑过
{
visit[v] = true;
if(linker[v] == -1 || dps(linker[v])) //假如没有配对过(直接匹配成功)或者它的配对对象可以再找到另一个新的配对对象
{
linker[v] = u;
return true;
}
}
return false; //找不到
}
DAG图的最小路径覆盖
DAG图:有向无环图
最小路径覆盖:用最少的路径覆盖所有的点
基本思想
将每一个点复制一份,把一个点列变成两个相同的点列,强行转换成二分图。因为每次找到一个匹配(想想男女配对中的匈牙利算法),都会减少一条需要走的路径(以下面伞兵的例子来说就是:每次找到一个匹配都会让同一个伞兵多走一段路,因此就可以少用一个伞兵)。所以最小路径数就等于顶点数减去最大匹配数(二分图的最大独立集)。
经典例题
空袭
#include <cstdio>
#include <cstring>
using namespace std;
int uNum, vNum, k, linker[501], testNum;
bool graph[501][501], visit[501];
int hungary();
bool dps(int);
int main()
{
scanf("%d", &testNum);
while(testNum--)
{
scanf("%d", &uNum);
vNum = uNum;
scanf("%d", &k);
int u, v;
memset(graph, false, sizeof(graph));
while(k--)
{
scanf("%d%d", &u, &v);
if(u != 0 && v != 0)
graph[u][v] = true;
}
printf("%d\n", uNum-hungary());
}
return 0;
}
int hungary()
{
int res = 0;
memset(linker, -1, sizeof(linker));
for(int u = 1; u <= uNum; u++)
{
memset(visit, false, sizeof(visit));
if(dps(u)) res++;
}
return res;
}
bool dps(int u)
{
for(int v = 1; v <= vNum; v++)
if(graph[u][v] && !visit[v])
{
visit[v] = true;
if(linker[v] == -1 || dps(linker[v]))
{
linker[v] = u;
return true;
}
}
return false;
}