目录
HDU1083——Courses
题目描述
运行代码
#include <cstdio>
#include <algorithm>
#include <vector>
#include<iostream>
#include<string.h>
using namespace std;
#define N 505
#define INF 0x3f3f3f3f
int n, p;
bool edge[N][N], vis[N];
int match[N];
int dfs(int u) {
for (int i = 1; i <= n; i++) {
if (!vis[i] && edge[u][i]) {
vis[i] = true;
if (!match[i] || dfs(match[i])) {
match[i] = u;
return 1;
}
}
}
return 0;
}
int main() {
int T;
scanf_s("%d", &T);
while (T--) {
fill(edge[0], edge[0] + N * N, false);
fill(vis, vis + N, false);
fill(match, match + N, 0);
scanf_s("%d%d", &p, &n);
for (int i = 1; i <= p; i++) {
int t;
scanf_s("%d", &t);
for (int j = 1; j <= t; j++) {
int u;
scanf_s("%d", &u);
edge[i][u] = true;
}
}
int sum = 0;
for (int i = 1; i <= p; i++) {
fill(vis, vis + N, false);
if (dfs(i)) {
sum++;
}
}
if (sum == p) {
printf("YES\n");
}
else {
printf("NO\n");
}
}
return 0;
}
代码思路
-
首先定义了一些常量和变量:
N
表示最大的节点数量。INF
作为一个较大的常数值。n
表示学生数量,p
表示课程数量。edge[N][N]
是一个二维布尔矩阵,表示课程和学生之间的关联。vis[N]
用于标记学生是否已被访问。match[N]
用于记录学生与课程的匹配关系。
-
dfs
函数用于深度优先搜索尝试进行匹配:- 从当前课程
u
开始,遍历所有学生i
。 - 如果学生
i
未被访问且与课程u
有关联(edge[u][i]
为true
),则将其标记为已访问。 - 如果学生
i
没有匹配的课程,或者可以通过递归调整匹配关系使得当前匹配成立,就更新匹配关系match[i] = u
并返回1
表示成功匹配。 - 如果遍历完都没有找到合适的匹配,返回
0
。
- 从当前课程
-
main
函数:- 输入测试用例数量
T
。在每个测试用例中:- 初始化
edge
、vis
和match
数组。 - 输入课程数量
p
和学生数量n
。 - 输入每个课程关联的学生。
- 通过一个循环对每个课程调用
dfs
函数尝试匹配,并累计成功匹配的数量sum
。 - 根据
sum
是否等于课程数量p
,输出YES
或NO
表示是否能够形成满足条件的委员会。
- 初始化
- 输入测试用例数量
匈牙利算法的匹配过程
准备阶段:构建关系矩阵(以二分图匹配为例)
假设我们有两个集合(比如人员集合和任务集合),如果人员和任务之间存在可能的关联(如人员可以执行某个任务)则构建一个二维矩阵来表示这种关系,矩阵元素可以是成本(如果是求最小成本匹配)、关联强度等。
核心步骤
-
行(列)规约(可选但常见步骤)
- 从每行中减去该行的最小元素 :这样做的目的是使得每行至少有一个元素为0,且不改变行之间元素相对大小关系,为后续匹配0元素做准备。
- 类似地从每列中减去该列的最小元素。
-
匹配尝试阶段(关键循环)
- 开始尝试以最少的线条(行线或列线)去覆盖所有的0元素。
- 如果覆盖所有0元素的线条数量等于矩阵的维度(如果是方阵就是行列数,如果是一般的m*n矩阵,看具体要求的匹配完整度等),那么就得到了最终的匹配结果,算法结束。
- 如果线条数量小于维度:
- 找到当前没有被覆盖的元素中的最小元素值(称为关键值)。
- 对所有未被覆盖的行减去这个关键值。
- 对所有被覆盖了两次(即被行线和列线交叉覆盖)的列加上这个关键值 (这样可以保证矩阵中0元素的分布和匹配关系会发生改变,在下一轮的尝试中可能会有新的匹配机会和路径出现)。
- 开始尝试以最少的线条(行线或列线)去覆盖所有的0元素。
-
实际匹配
当满足结束条件后,开始进行实际匹配:- 从只含有一个0元素的行开始,如果某行只有一个0元素,那么就将该行对应的元素进行匹配(比如人员和任务对应起来,如果是在二分图中就是将边连接起来),然后将该0元素所在的列和行的其他元素都标记为不可用(因为一个人只能做一个任务,一个任务只能被一个人做等类似的唯一性要求)。
- 然后逐行逐列按照这种方式继续进行匹配,直到无法找到更多的匹配为止。
解释其原理和意义方面
从原理上,通过不断调整矩阵中0元素的分布和通过覆盖线条数来判断是否达到最大(或完整)匹配,是基于这样的逻辑:每次调整都在尝试探索新的匹配路径和组合。
意义在于它在很多场景下有巨大作用,比如:
- 在任务分配场景中,合理地将任务分配给人员使得成本最小、效率最高等。
- 在计算机视觉中的目标跟踪数据关联中,将当前帧的检测目标和上一帧跟踪目标进行有效匹配以实现持续稳定跟踪等。