目录

  • 1. Fire Net HDU - 1045 (搜索 or 匹配)
  • 2. The Accomodation of Students HDU - 2444(二分图判断)
  • 3. Courses HDU - 1083
  • 4. 棋盘游戏 HDU - 1281
  • 5. Swap HDU - 2819
  • 6. Rain on your Parade HDU - 2389 (Hopcroft-Carp)
  • 7. Oil Skimming HDU - 4185
  • 8. Antenna Placement POJ - 3020(最小边覆盖)
  • 9. Strategic Game HDU - 1054(最小点覆盖)
  • 10. Air Raid HDU - 1151(DAG可相交最小路径覆盖)
  • 11. Treasure Exploration POJ - 2594
  • 12. Cat VS Dog HDU - 3829(最大独立集)
  • 13. Jamie's Contact Groups POJ - 2289(多重匹配)
  • 14. Optimal Milking POJ - 2112
  • 15. Steady Cow Assignment POJ - 3189
  • 16. 奔小康赚大钱 HDU - 2255(KM 算法)
  • 17. Tour HDU - 3488(有向有环图的最小路径覆盖+KM算法)
  • 18. Work Scheduling URAL - 1099(普通图最大匹配:带花树算法)
  • 19. Boke and Tsukkomi HDU - 4687

1. Fire Net HDU - 1045 (搜索 or 匹配)


传送门

题意
给出一个 n × n n \times n n×n 棋盘,其中有一些障碍物,所放的物品在不相隔至少一个障碍物的情况下不能同行或同列,要求最多能放多少物品。

题解
法1:
数据很小,可以直接搜索,一行一行走,每格填和不填都给试一遍。

法2:
因为每选一个格子就会占用与该格子同行、同列且连续的所有格子,
所以把分别每行中连续的部分缩成点作为二分图左部,每列中连续的部分缩成点作为右部,最大匹配即为答案。

dfs搜索:

// #include ...
const int N = 10;int n, res;
char s[N][N];
bool check(int x, int y) {int nx = x, ny = y, flg = 0;while (nx + 1 < n && s[nx + 1][y] != 'X') ++nx, flg |= (s[nx][y] == 'o');nx = x;while (nx - 1 >= 0 && s[nx - 1][y] != 'X') --nx, flg |= (s[nx][y] == 'o');while (ny - 1 >= 0 && s[x][ny - 1] != 'X') --ny, flg |= (s[x][ny] == 'o');ny = y;while (ny + 1 < n && s[x][ny + 1] != 'X') ++ny, flg |= (s[x][ny] == 'o');return !flg;
}
void dfs(int cur, int num) {// cur ∈ [0, n * n)if (cur == n * n) {res = max(res, num);return;}int x = cur / n, y = cur % n;if (s[x][y] == '.' && check(x, y)) {s[x][y] = 'o';dfs(cur + 1, num + 1);s[x][y] = '.';}dfs(cur + 1, num);
}int main() {// freopen("in.in", "r", stdin);while (~scanf("%d", &n) && n) {res = 0;for (int i = 0; i < n; i++) {scanf("%s", s[i]);}dfs(0, 0);printf("%d\n", res);}return 0;
}

二分图匹配:

// #include ...
const int N = 10;int n;
char s[N][N];
int cl[N][N], e[N][N], L, R;
int vis[N], b[N];
bool dfs(int x) {for (int i = 1; i <= R; i++) if (e[x][i]) {if (vis[i]) continue;vis[i] = 1;if (!b[i] || dfs(b[i])) {b[i] = x;return true;}}return false;
}
void init() {L = R = 0;memset(e, 0, sizeof(e));memset(b, 0, sizeof(b));memset(cl, 0, sizeof(cl));
}int main() {// freopen("in.in", "r", stdin);while (~scanf("%d", &n) && n) {init();for (int i = 1; i <= n; i++) {scanf("%s", s[i] + 1);}for (int i = 1; i <= n; i++) {for (int j = 1; j <= n; j++) {if (s[i][j] == '.') {if (s[i][j] != s[i][j - 1]) ++L;cl[i][j] = L;}}}for (int j = 1; j <= n; j++) {for (int i = 1; i <= n; i++) {if (s[i][j] == '.') {if (s[i][j] != s[i - 1][j]) ++R;e[cl[i][j]][R] = 1;// 左部点 -> 右部点}}}int res = 0;for (int i = 1; i <= L; i++) {memset(vis, 0, sizeof(vis));res += dfs(i);}printf("%d\n", res);}return 0;
}

2. The Accomodation of Students HDU - 2444(二分图判断)


传送门

题意
n n n 个学生, m m m 对互相认识,问是否能分成两组,使得组中的人都不互相认识,若不彳亍就输出NO,彳亍则将互相认识的分进双人间,求最多能分多少双人间。

错误题解
因为只能认识的人在一间房,所以双人间计数的问题对于每一个连通块相互独立,可以分开单独讨论。

对于一个连通块:

  • 若不满足二分图则直接NO。
  • 满足二分图的情况下, 01 01 01 染色后出现次数较少的颜色数目就是能够匹配的最多数目,累加进答案中。

hack :

正解
首先 01 01 01 染色判二分图,之后将 0 0 0, 1 1 1 分别作左右部跑一遍最大匹配即是答案。

Code:

// #include ...
const int N = (int)(4e4) + 7;int n, m;
int hd[N], cnt;
int cl[N], vis[N], b[N], flg;
struct edge {int to, nxt;
} e[N << 1];
void add(int u, int v) {e[++cnt] = { v, hd[u] };hd[u] = cnt;
}
void check(int x, int cur) {if (cl[x]) {if (cl[x] != cur + 1) flg = 1;return;}cl[x] = cur + 1;for (int i = hd[x]; i; i = e[i].nxt) check(e[i].to, (cur ^ 1));
}
bool dfs(int x) {for (int i = hd[x]; i; i = e[i].nxt) {int to = e[i].to;if (vis[to]) continue;vis[to] = 1;if (!b[to] || dfs(b[to])) {b[to] = x;return true;}}return false;
}
void init() {flg = cnt = 0;memset(b, 0, sizeof(b));memset(cl, 0, sizeof(cl));memset(hd, 0, sizeof(hd));
}int main() {// freopen("in.in", "r", stdin);while (~scanf("%d%d", &n, &m)) {init();for (int i = 1; i <= m; ++i) {int u, v;scanf("%d%d", &u, &v);add(u, v); add(v, u);}for (int i = 1; i <= n; ++i) {if (!cl[i]) {check(i, 0);if (flg) break;}}if (flg) puts("No");else {int res = 0;for (int i = 1; i <= n; ++i) if (cl[i] == 1) {memset(vis, 0, sizeof(vis));res += dfs(i);}printf("%d\n", res);}}return 0;
}

3. Courses HDU - 1083


传送门

题意
P P P 门课程, N N N 个学生,给出每个课程被哪些学生参加过的列表,
判断是否能选出 P P P 个学生组成委员会,要求:

  • 委员会中每个人都分别代表着不同的课程。
  • 每个课程都有一个人作为代表。

题解
课程当左部,学生当右部,求出最大匹配数,若等于 P P P 则彳亍,否则不彳亍。

Code:

// #include ...
const int N = 300 + 7;int _, p, n;
int vis[N], b[N], e[N][N];
bool dfs(int x) {for (int i = 1; i <= n; i++) if (e[x][i]) {if (vis[i]) continue;vis[i] = 1;if (!b[i] || dfs(b[i])) {b[i] = x;return true;}}return false;
}
void init() {memset(e, 0, sizeof(e));memset(b, 0, sizeof(b));memset(vis, 0, sizeof(vis));
}int main() {// freopen("in.in", "r", stdin);for (scanf("%d", &_); _; _--) {scanf("%d%d", &p, &n);init();for (int i = 1; i <= p; i++) {int k;scanf("%d", &k);for (int j = 1; j <= k; j++) {int v;scanf("%d", &v);e[i][v] = 1;}}int res = 0;for (int i = 1; i <= p; i++) {memset(vis, 0, sizeof(vis));res += dfs(i);}puts(res == p ? "YES" : "NO");}return 0;
}

4. 棋盘游戏 HDU - 1281


传送门

题意
给出一个 N × M N \times M N×M 的棋盘,有 K K K 个位置能放「车」。

  • 要求放尽可能多的「车」使得没有棋子可以相互攻击。
  • 若一个格子不放棋子就没办法保证尽可能多放,这个格子就被称作「重要点」,求「重要点」的数目。

题解

  • 每个能放「车」的坐标可以看做可行的行与列的一对匹配,行与列最大匹配就是最多能放的数量。
  • 每次在原图基础上删掉其中一个可行匹配check,再求一次最大匹配,若匹配数减少了则记录为「重要点」。

Code :

// #include ...
const int N = (int)(2e4) + 7;int n, m, k;
int hd[N], cnt;
int vis[N], b[N], del;
struct edge {int to, nxt, fr;
} e[N];
void add(int u, int v) {e[++cnt] = { v, hd[u], u };hd[u] = cnt;
}
bool dfs(int x) {for (int i = hd[x]; i; i = e[i].nxt) {if (i == del) continue;int to = e[i].to;if (vis[to]) continue;vis[to] = 1;if (!b[to] || dfs(b[to])) {b[to] = x;return true;}}return false;
}
int hungary() {memset(b, 0, sizeof(b));int res = 0;for (int i = 1; i <= n; i++) {memset(vis, 0, sizeof(vis));res += dfs(i);}return res;
}
void init() {cnt = del = 0;memset(b, 0, sizeof(b));memset(hd, 0, sizeof(hd));memset(vis, 0, sizeof(vis));
}int main() {// freopen("in.in", "r", stdin);int cs = 0;while (~scanf("%d%d%d", &n, &m, &k)) {init();for (int i = 1; i <= k; i++) {int x, y;scanf("%d%d", &x, &y);add(x, y);}int res = hungary(), imp = 0;for (int i = 1; i <= k; i++) {del = i;if (hungary() != res) imp++;}printf("Board %d have %d important blanks for %d chessmen.\n", ++cs, imp, res);}return 0;
}

5. Swap HDU - 2819


传送门

题意
给出一个 n × n n \times n n×n 的 01 01 01 矩阵,每次操作可以交换任意两行或任意两列,判断是否能使对角线上全是 1 1 1,能则输出步数以及具体方案。

题解
要使对角线上都能被 1 1 1 覆盖,不同行且不同列的 1 1 1 至少有 n n n 个,把 a [ i ] [ j ] = 1 a[i][j] = 1 a[i][j]=1 当做一条匹配的边时,行列的最大匹配需要为 n n n。
所以若求得最大匹配不为 n n n 时,直接 -1了。
为 n n n 时,把匹配到的 n n n 个点在图上标出来,之后模拟行变换即可。

Code :

// #include ...
const int N = 100 + 7;vector <Pii> v;
int n, a[N][N], b[N], vis[N];
bool dfs(int x) {for (int i = 1; i <= n; i++) if (a[x][i]) {if (vis[i]) continue;vis[i] = 1;if (!b[i] || dfs(b[i])) {b[i] = x;return true;}}return false;
}
void init() {v.clear();memset(b, 0, sizeof(b));memset(vis, 0, sizeof(vis));
}int main() {// freopen("in.in", "r", stdin);while (~scanf("%d", &n)) {init();for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) scanf("%d", &a[i][j]);int res = 0;for (int i = 1; i <= n; i++) {memset(vis, 0, sizeof(vis));res += dfs(i);}if (res == n) {for (int i = 1; i <= n; i++) a[b[i]][i] = 2;for (int i = 1; i <= n; i++) {if (a[i][i] == 2) continue;for (int j = i + 1; j <= n; j++) if (a[j][i] == 2) {v.pb(mp(i, j));for (int k = 1; k <= n; k++) {swap(a[i][k], a[j][k]);}}}int si = SZ(v);printf("%d\n", si);for (int i = 0; i < si; i++) printf("R %d %d\n", v[i].fir, v[i].sec);} else puts("-1");}return 0;
}

6. Rain on your Parade HDU - 2389 (Hopcroft-Carp)


传送门

题意
还有 t t t 分钟就要下雨,给出 m m m 个人的 x x x, y y y 坐标和每分钟的速度 s s s, n n n 把只能一个人用的雨伞的坐标。
问最多能有多少人能顺利避雨。

题解
根据每个人与每把雨伞的速度与距离判断是否连边,跑一边最大匹配即可。这里时空都卡的很紧,得用 Hopcroft-Carp 算法(主要思想是在跑匈牙利之前先 bfs 对当前未匹配点增广路径分层预处理,和 dinic 比较类似)

// #include ...
const int inf = 0x3f3f3f3f;
const int N = 3000 + 7;int n, m, t;
int hd[N], cnt;
int mx[N], my[N], vis[N], dx[N], dy[N], dis;
struct node {int x, y, s;
} nd[N], um[N];
struct edge {int to, nxt;
} e[N * N];
void add(int u, int v) {e[++cnt] = { v, hd[u] };hd[u] = cnt;
}
bool bfs() {queue <int> q;memset(dx, -1, sizeof(dx));memset(dy, -1, sizeof(dy));dis = inf;for (int i = 1; i <= m; ++i) {if (mx[i] == -1) {// 寻找未匹配点的增广路q.push(i);dx[i] = 0;}}while (!q.empty()) {int x = q.front(); q.pop();if (dx[x] > dis) break;for (int i = hd[x]; i; i = e[i].nxt) {int to = e[i].to;if (dy[to] == -1) {dy[to] = dx[x] + 1;if (my[to] == -1) dis = dy[to];// 能够匹配else {// 继续找dx[my[to]] = dy[to] + 1;        q.push(my[to]);}}}}return dis != inf;
}
bool dfs(int x) {for (int i = hd[x]; i; i = e[i].nxt) {int to = e[i].to;if (!vis[to] && dy[to] == dx[x] + 1) {vis[to] = 1;if (~my[to] && dy[to] == dis) continue;// 最后一个点肯定找不到了if (my[to] == -1 || dfs(my[to])) {my[to] = x;mx[x] = to;return true;}}}return false;
}
int maxmatch() {memset(mx, -1, sizeof(mx));memset(my, -1, sizeof(my));int res = 0;while (bfs()) {memset(vis, 0, sizeof(vis));for (int i = 1; i <= m; ++i) {if (mx[i] == -1 && dfs(i)) res++;}}return res;
}
void init() {dis = cnt = 0;memset(hd, 0, sizeof(hd));
}int main() {// freopen("in.in", "r", stdin);int _, cs = 0;for (scanf("%d", &_); _; --_) {init();scanf("%d%d", &t, &m);for (int i = 1; i <= m; ++i) {scanf("%d%d%d", &nd[i].x, &nd[i].y, &nd[i].s);}scanf("%d", &n);for (int i = 1; i <= n; ++i) {scanf("%d%d", &um[i].x, &um[i].y);}for (int i = 1; i <= m; ++i) {for (int j = 1; j <= n; ++j) {int x = nd[i].x - um[j].x;int y = nd[i].y - um[j].y;int s = nd[i].s * t;if (x * x + y * y <= s * s) add(i, j);}}printf("Scenario #%d:\n%d\n\n", ++cs, maxmatch());}return 0;
}

7. Oil Skimming HDU - 4185


传送门

题意
给出 N × N N \times N N×N 的 ‘#’ 与 ‘.’ 组成的方格图,每次可以在 1 × 2 1 \times 2 1×2 大小全是 ‘#’ 的方块上放物品,求最多放多少物品。

题解
按 i + j i + j i+j 的奇偶性来分左右部,相邻的 ‘#’ 间有连边,匈牙利算法求二分图最大匹配即可。

Code :

// include ...
const int N = 600 + 7;Pii b[N][N];
char s[N][N];
int n, vis[N][N];
int dx[] = { -1, 1, 0, 0 }, dy[] = { 0, 0, -1, 1 };
bool dfs(int x, int y) {for (int i = 0; i < 4; ++i) {int tx = x + dx[i], ty = y + dy[i];if (tx < 1 || tx > n || ty < 1 || ty > n) continue;if (s[tx][ty] == '#' && !vis[tx][ty]) {vis[tx][ty] = 1;if (!b[tx][ty].fir || dfs(b[tx][ty].fir, b[tx][ty].sec)) {b[tx][ty] = mp(x, y);return true;}}}return false;
}
void init() {for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) b[i][j].fir = 0;
}int main() {// freopen("in.in", "r", stdin);int _, cs = 0;for (scanf("%d", &_); _; --_) {scanf("%d", &n);init();for (int i = 1; i <= n; ++i) {scanf("%s", s[i] + 1);}int res = 0;for (int i = 1; i <= n; ++i) {for (int j = 1; j <= n; ++j) {if (((i + j) & 1) && s[i][j] == '#') {memset(vis, 0, sizeof(vis));res += dfs(i, j);}}}printf("Case %d: %d\n", ++cs, res);}return 0;
}

8. Antenna Placement POJ - 3020(最小边覆盖)


传送门

题意
给出 h × w h \times w h×w 的方格图,每次可以在任意位置放一个 1 × 2 1 \times 2 1×2 大小的物品,求最少的物品数量使得图中所有的 ‘*’ 被覆盖。

题解
还是按 ( i + j ) (i + j) (i+j) 的奇偶性把整张图划分为二分图左右部,相邻的点即是二分图的边,此题就化为二分图中的最小边覆盖问题。
一条边最多能覆盖两个点,所以贪心地想,设总点数为 n n n,最大匹配数为 k k k,则首先可以用 k k k 条边覆盖 2 k 2k 2k 个点,最后剩下 n − 2 k n - 2k n−2k 个点只能一条边覆盖一个点了,最后答案就为 n − k n - k n−k。

Code :

// #include ...
const int N = 40 + 7;Pii b[N][N];
char s[N][N];
int n, m, vis[N][N];
int dx[] = { -1, 1, 0, 0 }, dy[] = { 0, 0, -1, 1 };
bool dfs(int x, int y) {for (int i = 0; i < 4; ++i) {int tx = x + dx[i], ty = y + dy[i];if (tx < 1 || tx > n || ty < 1 || ty > m) continue;if (s[tx][ty] == '*' && !vis[tx][ty]) {vis[tx][ty] = 1;if (!b[tx][ty].fir || dfs(b[tx][ty].fir, b[tx][ty].sec)) {b[tx][ty] = mp(x, y);return true;}}}return false;
}
void init() {for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) b[i][j].fir = 0;
}int main() {// freopen("in.in", "r", stdin);int _;for (scanf("%d", &_); _; --_) {scanf("%d%d", &n, &m);init();int sum = 0;for (int i = 1; i <= n; ++i) {scanf("%s", s[i] + 1);for (int j = 1; j <= m; ++j) {if (s[i][j] == '*') sum++;}}int res = 0;for (int i = 1; i <= n; ++i) {for (int j = 1; j <= m; ++j) {if (s[i][j] == '*' && ((i + j) & 1)) {memset(vis, 0, sizeof(vis));res += dfs(i, j);}}}printf("%d\n", sum - res);}    return 0;
}

9. Strategic Game HDU - 1054(最小点覆盖)


传送门

题意
给出一棵树,要求在节点上放置最少的物品,使得每条边都能被覆盖(至少有一个端点有物品)。

题解
最小点覆盖 = 最大匹配,相关证明。
这里没用二分图的判定,所以直接把所有点同时当做左右部,自己匹配自己,此时二分图是对称的,最后得到的结果除二即是自己内部点的最大匹配。

Code :

// #include ...
const int N = 1500 + 7;int n;
int hd[N], cnt;
int b[N], vis[N];
struct edge {int to, nxt;
} e[N << 1];
void add(int u, int v) {e[++cnt] = { v, hd[u] };hd[u] = cnt;
}
bool dfs(int x) {for (int i = hd[x]; i; i = e[i].nxt) {int to = e[i].to;if (vis[to]) continue;vis[to] = 1;if (b[to] == -1 || dfs(b[to])) {b[to] = x;return true;}}return false;
}
int maxmatch() {int res = 0;for (int i = 0; i < n; ++i) {memset(vis, 0, sizeof(vis));res += dfs(i);}return res;
}
void init() {cnt = 0;memset(b, -1, sizeof(b));memset(hd, 0, sizeof(hd));
}
int main() {// freopen("in.in", "r", stdin);while (~scanf("%d", &n)) {init();for (int i = 1; i <= n; ++i) {int u, k;scanf("%d:(%d)", &u, &k);for (int j = 1; j <= k; ++j) {int v;scanf("%d", &v);add(u, v); add(v, u);}}printf("%d\n", maxmatch() / 2);}return 0;
}

10. Air Raid HDU - 1151(DAG可相交最小路径覆盖)


传送门

题意
给出有向无环图,每个伞兵可以 visit 一条路径,
求至少要多少伞兵可以 visit 到所有的点,路径可相交。

题解
本质上就是选出可最少能够覆盖到所有点的可相交路径,为DAG的可相交最小路径覆盖问题。
首先求一次传递闭包,把能够到达的路径都连上边,再自己和自己匹配(不用除二,因为左右部分别表示起点和终点),用答案就是总点数减去最大匹配数。

Code :

// #include ...
const int N = 120 + 7;int n, m;
int e[N][N], b[N], vis[N];
void init() {memset(b, 0, sizeof(b));memset(e, 0, sizeof(e));
}
bool dfs(int x) {for (int i = 1; i <= n; ++i) {if (e[x][i] && !vis[i]) {vis[i] = 1;if (!b[i] || dfs(b[i])) {b[i] = x;return true;}}}return false;
}int main() {// freopen("in.in", "r", stdin);int _;for (scanf("%d", &_); _; --_) {scanf("%d%d", &n, &m);init();for (int i = 1; i <= m; ++i) {int u, v;scanf("%d%d", &u, &v);e[u][v] = 1;}for (int k = 1; k <= n; ++k) // 传递闭包for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) e[i][j] |= (e[i][k] & e[k][j]);int res = 0;for (int i = 1; i <= n; ++i) {memset(vis, 0, sizeof(vis));res += dfs(i);}printf("%d\n", n - res);}return 0;
}

11. Treasure Exploration POJ - 2594


传送门

题意
给出一个有向无环图,可以放一些机器人,一个机器人能够探索一条路径,要求输出能够探索所有点的机器人数目,路径可相交。

题解
和上题一样,也是可相交的最小路径覆盖。

Code :

// #include ...
const int N = 500 + 7;int n, m;
int e[N][N], b[N], vis[N];
void init() {memset(e, 0, sizeof(e));memset(b, 0, sizeof(b));
}
bool dfs(int x) {for (int i = 1; i <= n; ++i) {if (e[x][i] && !vis[i]) {vis[i] = 1;if (!b[i] || dfs(b[i])) {b[i] = x;return true;}}}return false;
}int main() {// freopen("in.in", "r", stdin);while (~scanf("%d%d", &n, &m) && (n || m)) {init();for (int i = 1; i <= m; ++i) {int u, v;scanf("%d%d", &u, &v);e[u][v] = 1;}for (int k = 1; k <= n; ++k) for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) e[i][j] |= (e[i][k] & e[k][j]);int res = 0;for (int i = 1; i <= n; ++i) {memset(vis, 0, sizeof(vis));res += dfs(i);}printf("%d\n", n - res);}return 0;
}

12. Cat VS Dog HDU - 3829(最大独立集)


传送门

题意
有 n n n 猫, m m m 狗,给出 p p p 个孩子喜欢的某一个动物和不喜欢的某一个动物,(喜欢猫的必定不喜欢狗,反之亦然)。若孩子喜欢的动物没被去掉而不喜欢的被去掉了,这个孩子就快乐,问最大快乐人数。

题解
可以将不能同时快乐的孩子互相连边,(喜欢的和别人不喜欢的相同),答案就是最大独立集(总数减最大匹配数),最大的没有连上矛盾关系的点集。

Code :

// #include ...
const int N = 500 + 7;int n, m, p;
char x[N][5], y[N][5];
int b[N], e[N][N], vis[N];
bool dfs(int x) {for (int i = 1; i <= p; ++i) {if (e[x][i] && !vis[i]) {vis[i] = 1;if (!b[i] || dfs(b[i])) {b[i] = x;return true;}}}return false;
}
void init() {memset(b, 0, sizeof(b));memset(e, 0, sizeof(e));
}int main() {// freopen("in.in", "r", stdin);while (~scanf("%d%d%d", &n, &m, &p)) {init();for (int i = 1; i <= p; ++i) {scanf("%s%s", x[i], y[i]);}for (int i = 1; i <= p; ++i) // 只要矛盾就建双向边for (int j = i; j <= p; ++j) if (!strcmp(x[i], y[j]) || !strcmp(x[j], y[i])) e[i][j] = e[j][i] = 1;int res = 0;for (int i = 1; i <= p; ++i) {memset(vis, 0, sizeof(vis));res += dfs(i);}printf("%d\n", p - res / 2);// 除 2 去掉重复的最大匹配}return 0;
}

13. Jamie’s Contact Groups POJ - 2289(多重匹配)


传送门

题意
n n n 个朋友和 m m m 个分组,给出每个人可以所属的分组,要求使每个朋友只属于一个分组,输出最大组的最小值。

题解
根据最大值的最小化就可以意识到这可能是个二分问题,

由题可得,每组最多分的人数如果越少那越难满足题意。

所以通过二分最大组的最小值,再用多重匹配 check即可。

Code :

// include ...
#define pb push_back
#define SZ(a) (int)((a).size())
const int N = 1000 + 7;
const int M = (int)(5e5) + 7;char name[20], ch;
int n, m, cnt;
int hd[N], vis[N];
vector <int> b[N];// 匹配标记(b[i] 表示 i 匹配的人们)
struct edge {int to, nxt;edge() {}edge(int a, int b): to(a), nxt(b) {}
} e[M];
void add(int u, int v) {e[++cnt] = edge(v, hd[u]);hd[u] = cnt;
}
bool dfs(int x, int lim) {for (int i = hd[x]; i; i = e[i].nxt) {int to = e[i].to;if (vis[to]) continue;vis[to] = 1;int si = SZ(b[to]);if (si < lim) {// 还能匹配b[to].pb(x);return true;}for (int j = 0; j < si; ++j) {if (dfs(b[to][j], lim)) {b[to][j] = x;// 替换return true;}}}return false;
}
bool check(int lim) {for (int i = 1; i <= m; ++i) b[i].clear();for (int i = 1; i <= n; ++i) {memset(vis, 0, sizeof(vis));if (!dfs(i, lim)) return false;// 有一个不匹配就噶了}return true;
}
void init() {cnt = 0;memset(hd, 0, sizeof(hd));
}int main() {// freopen("in.in", "r", stdin);while (~scanf("%d%d", &n, &m) && (n || m)) {init();for (int i = 1; i <= n; ++i) {scanf("%s", name);while ((ch = getchar()) == ' ') {int v;scanf("%d", &v); ++v;add(i, v);}}int L = 0, R = n;while (L < R) {int mid = (L + R) >> 1;if (check(mid)) R = mid;else L = mid + 1;}printf("%d\n", L);}return 0;
}

14. Optimal Milking POJ - 2112


传送门

题意
C C C 头奶牛, K K K 台 milking machine,每台机器最多被 M M M 头奶牛占用。

给出每头奶牛距每台机器的距离,求机器不过度使用的情况下奶牛所走最远距离的最小值。

题解
同第 13 13 13 题,二分 + 多重匹配问题,多了个 floyd 预处理,还有一些特判。
最远距离的越大越容易满足题意,因为题目已经保证了存在解,所以不用担心距离再大的时候会因为过度使用机器而不满足题意。

Code :

// include ...
const int N = 250 + 7;int k, c, m;
int e[N][N], vis[N];
vector <int> b[N];
bool dfs(int x, int di) {for (int i = 1; i <= k; ++i) {// e[i][j] 为 0 表示没有路径if (e[x][i] && e[x][i] <= di && !vis[i]) {vis[i] = 1;int si = SZ(b[i]);if (si < m) {b[i].pb(x);return true;}for (int j = 0; j < si; ++j) {if (dfs(b[i][j], di)) {b[i][j] = x;return true;}}}}return false;
}
bool check(int di) {for (int i = 1; i <= k; ++i) b[i].clear();for (int i = k + 1; i <= k + c; ++i) {memset(vis, 0, sizeof(vis));if (!dfs(i, di)) return false;}return true;
}
void floyd() {for (int l = 1; l <= k + c; ++l) for (int i = 1; i <= k + c; ++i) for (int j = 1; j <= k + c; ++j) if (e[i][l] && e[l][j] && (!e[i][j] || e[i][j] > e[i][l] + e[l][j])) e[i][j] = e[i][l] + e[l][j];
}int main() {// freopen("in.in", "r", stdin);scanf("%d%d%d", &k, &c, &m);for (int i = 1; i <= k + c; ++i) for (int j = 1; j <= k + c; ++j) scanf("%d", &e[i][j]);floyd();int ma = 0;for (int i = 1; i <= k + c; ++i) for (int j = 1; j <= k + c; ++j) ma = max(ma, e[i][j]);int L = 0, R = ma;while (L < R) {int mid = (L + R) >> 1;if (check(mid)) R = mid;else L = mid + 1;}printf("%d", L);return 0;
}

15. Steady Cow Assignment POJ - 3189


传送门

题意
N N N 头 cow, B B B 个 barn, 每个 barn 都有一个容量,给出每头 cow 心中 barn 的排名,要求在不超过容量的情况下使所有 cow 所匹配的 barn 排名范围最小。

题解
这题 B B B 的数据量比较小,直接从小到大枚举区间,对于每一个区间都用多重匹配 check 就能糊过。

// include ...
const int N = 1000 + 7;int n, ba;
int e[N][N], c[N], vis[N];
vector <int> b[N];
bool dfs(int x, int l, int r) {for (int i = 1; i <= ba; ++i) {if (l <= e[x][i] && e[x][i] <= r && !vis[i]) {vis[i] = 1;int si = SZ(b[i]);if (si < c[i]) {b[i].pb(x);return true;}for (int j = 0; j < si; ++j) {if (dfs(b[i][j], l, r)) {b[i][j] = x;return true;}}}}return false;
}
bool check(int l, int r) {for (int i = 1; i <= ba; ++i) b[i].clear();for (int i = 1; i <= n; ++i) {memset(vis, 0, sizeof(vis));if (!dfs(i, l, r)) return false;}return true;
}int main() {// freopen("in.in", "r", stdin);scanf("%d%d", &n, &ba);for (int i = 1; i <= n; ++i) {for (int j = 1; j <= ba; ++j) {int v;scanf("%d", &v);e[i][v] = j;}}for (int i = 1; i <= ba; ++i) scanf("%d", &c[i]);for (int len = 1; len <= ba; ++len) {for (int L = 1; L + len - 1 <= ba; ++L) {int R = L + len - 1;if (check(L, R)) return printf("%d\n", len), 0;}}    return 0;
}

16. 奔小康赚大钱 HDU - 2255(KM 算法)


传送门

题意
n n n 家老百姓, n n n 套房子,给出每家老百姓能够对每套房子支付的价钱,并且每家老百姓只能匹配一套房子,输出支付价钱总和的最大值。

题解
带权图的最佳匹配,需要用到 KM算法。资源1,资源2。

Code :

// include ...
const int inf = 0x3f3f3f3f;
const int N = 300 + 7;int n;
int e[N][N], my[N], visx[N], visy[N];
// visx[i], visy[j] 表示一轮寻找中是否到过 i, j, my[i] 表示与 i 匹配的点
int slk[N], lx[N], ly[N];
// lx[i] 表示 i 点连出的最大边权
bool dfs(int x) {visx[x] = 1;for (int i = 1; i <= n; ++i) {if (e[x][i] && !visy[i]) {int t = lx[x] + ly[i] - e[x][i];if (!t) {visy[i] = 1;if (!my[i] || dfs(my[i])) {my[i] = x;return true;}} else if (slk[i] > t) {slk[i] = t;}}}return false;
}
int KM() {memset(my, 0, sizeof(my));memset(ly, 0, sizeof(ly));for (int i = 1; i <= n; ++i) {lx[i] = -inf;for (int j = 1; j <= n; ++j) // 取最大边权if (lx[i] < e[i][j]) lx[i] = e[i][j];}for (int i = 1; i <= n; ++i) {for (int j = 1; j <= n; ++j) slk[j] = inf;// 遍历右部while (1) {memset(visx, 0, sizeof(visx));memset(visy, 0, sizeof(visy));if (dfs(i)) break;int d = inf;for (int j = 1; j <= n; ++j) {// 右部if (!visy[j] && d > slk[j]) d = slk[j];// 取最小 slk 值}for (int j = 1; j <= n; ++j) {// 左部if (visx[j]) lx[j] -= d;}for (int j = 1; j <= n; ++j) {// 右部if (visy[j]) ly[j] += d;else slk[j] -= d;}}}int res = 0;for (int i = 1; i <= n; ++i) {if (my[i]) res += e[my[i]][i];}return res;
}int main() {// freopen("in.in", "r", stdin);while (~scanf("%d", &n)) {for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) scanf("%d", &e[i][j]);printf("%d\n", KM());}return 0;
}

17. Tour HDU - 3488(有向有环图的最小路径覆盖+KM算法)


传送门

题意
给出 N N N 个点, M M M 条带权边的有向图,要求给出一条路线经过所有点,使得路线长度最小。

题解
首先将边权变为相反数后对重边取最大值记录,将 N N N 个点拆开自己和自己匹配,KM 算法求出的最大值的相反数即为答案。

Code :

// include ...
const int inf = 0x3f3f3f3f;
const int N = 200 + 7;int _, n, m;
int lx[N], ly[N];
int visx[N], visy[N];
int e[N][N], my[N], slk[N];
void init() {memset(e, -0x3f, sizeof(e));
}
bool dfs(int x) {visx[x] = 1;for (int i = 1; i <= n; ++i) {if (!visy[i]) {int t = lx[x] + ly[i] - e[x][i];if (!t) {visy[i] = 1;if (!my[i] || dfs(my[i])) {my[i] = x;return true;}} else if (slk[i] > t) slk[i] = t;}}return false;
}
int KM() {memset(my, 0, sizeof(my));for (int i = 1; i <= n; ++i) {ly[i] = 0;lx[i] = -inf;for (int j = 1; j <= n; ++j) if (lx[i] < e[i][j]) lx[i] = e[i][j];}for (int i = 1; i <= n; ++i) {for (int j = 1; j <= n; ++j) slk[j] = inf;while (1) {memset(visx, 0, sizeof(visx));memset(visy, 0, sizeof(visy));if (dfs(i)) break;int d = inf;for (int j = 1; j <= n; ++j) {if (!visy[j] && d > slk[j]) d = slk[j];}for (int j = 1; j <= n; ++j) {if (visx[j]) lx[j] -= d;}for (int j = 1; j <= n; ++j) {if (visy[j]) ly[j] += d;else slk[j] -= d;}}}int res = 0;for (int i = 1; i <= n; ++i) {if (my[i]) res += e[my[i]][i];}return -res;
}int main() {// freopen("in.in", "r", stdin);for (scanf("%d", &_); _; --_) {init();scanf("%d%d", &n, &m);for (int i = 1; i <= m; ++i) {int u, v, w;scanf("%d%d%d", &u, &v, &w);e[u][v] = max(e[u][v], -w);}printf("%d\n", KM());}return 0;
}

18. Work Scheduling URAL - 1099(普通图最大匹配:带花树算法)


传送门

题意
给出 N N N 个点,若干对可行的匹配,要求匹配数最大,输出匹配方案。

题解
对于普通图,普通的匈牙利算法不能解决问题了。此时需要用到同样是通过寻找增广路不过特判了奇环情况的带花树算法,与二分图最大匹配相比,dfs也换成了 bfs。资源1,资源2。

Code :

// include ...
const int N = 222 + 7;int n;
int f[N];
int e[N][N], mat[N], typ[N], pre[N], vis[N], ti;
queue <int> q;
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
int lca(int x, int y) {// 交替爬树找 lcafor (++ti; ;swap(x, y)) if (x) {// vis[] 与 ti 作标记, 两边相遇即返回x = find(x);if (vis[x] == ti) return x;else vis[x] = ti, x = pre[mat[x]];}
}
void shrink(int x, int y, int fl) {while (find(x) != fl) {pre[x] = y, y = mat[x];// 边变成双向if (typ[y] == 2) typ[y] = 1, q.push(y);if (find(x) == x) f[x] = fl;if (find(y) == y) f[y] = fl;x = pre[y];}
}
bool bfs(int s) {for (int i = 1; i <= n; ++i) f[i] = i;while (!q.empty()) q.pop();// 每次寻找增广路记得初始化队列memset(typ, 0, sizeof(typ));memset(pre, 0, sizeof(pre));q.push(s);typ[s] = 1;while (!q.empty()) {int x = q.front(); q.pop();for (int i = 1; i <= n; ++i) if (e[x][i]) {if (find(i) == find(x) || typ[i] == 2) continue;if (!typ[i]) {typ[i] = 2, pre[i] = x;if (!mat[i]) {// 找到增广路, 回溯for (int cur = i, lst, t; cur; cur = lst) {lst = mat[t = pre[cur]];mat[cur] = t, mat[t] = cur;}return true;}typ[mat[i]] = 1, q.push(mat[i]);} else if (typ[i] == 1) {// 遇到奇环, 缩点int fl = lca(x, i);shrink(x, i, fl);shrink(i, x, fl);}}}return false;
}int main() {// freopen("in.in", "r", stdin);scanf("%d", &n);int u, v;while (~scanf("%d%d", &u, &v)) {e[u][v] = e[v][u] = 1;}int res = 0;for (int i = 1; i <= n; ++i) {if (!mat[i] && bfs(i)) ++res;// 匹配组数}printf("%d\n", res * 2);for (int i = 1; i <= n; ++i) {// i < mat[i] 保证每组匹配只输出一遍if (i < mat[i]) printf("%d %d\n", i, mat[i]);}return 0;
}

19. Boke and Tsukkomi HDU - 4687


传送门

题意
给出 N N N 个结点, M M M 个可行匹配,要求判断当中哪些匹配相对于最大匹配来说是多余的(不属于任何一个最大匹配)。

题解
同 18 18 18 题,普通图的最大匹配,用带花树算法,对每条边枚举,删去后(删去该边对应的两个点)看匹配数是否减少,若不减少则计入答案。

一开始以为枚举删边 check 最大匹配数是否减少,不减少则为多余,之后发现这和第 4 4 4 题问的问题不一样。

多余是指当前的边不属于任何一个最大匹配,所以当最大匹配数为 r e s res res 时,应该先假设当前边属于某一个最大匹配,之后删除该匹配两节点的所有信息,看剩下的匹配数是否为 r e s − 1 res - 1 res−1,若不是则说明多余。

Code :

// include ...
const int N = 40 + 7;int n, m;
int f[N], e[N][N], g[N][N];
int mat[N], vis[N], typ[N], pre[N], ti;
Pii ed[8 * N];
queue <int> q;
vector <int> ans;
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
void init() {ti = 0;ans.clear();memset(e, 0, sizeof(e));memset(g, 0, sizeof(g));for (int i = 1; i <= m; ++i) ed[i] = mp(0, 0);
}
int lca(int x, int y) {for (++ti; ; swap(x, y)) if (x) {x = find(x);if (vis[x] == ti) return x;else vis[x] = ti, x = pre[mat[x]];}
}
void shrink(int x, int y, int fl) {while (find(x) != fl) {pre[x] = y, y = mat[x];if (typ[y] == 2) typ[y] = 1, q.push(y);if (find(x) == x) f[x] = fl;if (find(y) == y) f[y] = fl;x = pre[y];}
}
bool bfs(int s) {for (int i = 1; i <= n; ++i) f[i] = i;while (!q.empty()) q.pop();memset(typ, 0, sizeof(typ));memset(pre, 0, sizeof(pre));q.push(s);typ[s] = 1;while (!q.empty()) {int x = q.front(); q.pop();for (int i = 1; i <= n; ++i) if (e[x][i]) {if (find(i) == find(x) || typ[i] == 2) continue;if (!typ[i]) {typ[i] = 2, pre[i] = x;if (!mat[i]) {for (int cur = i, lst, t; cur; cur = lst) {lst = mat[t = pre[cur]];mat[cur] = t, mat[t] = cur;}return true;}typ[mat[i]] = 1, q.push(mat[i]);} else if (typ[i] == 1) {int fl = lca(x, i);shrink(x, i, fl);shrink(i, x, fl);}}}return false;
}
int maxmatch() {memset(vis, 0, sizeof(vis));memset(mat, 0, sizeof(mat));int res = 0;for (int i = 1; i <= n; ++i) {if (!mat[i]) res += bfs(i);}return res;
}
int main() {// freopen("in.in", "r", stdin);while (~scanf("%d%d", &n, &m)) {init();for (int i = 1; i <= m; ++i) {int u, v;scanf("%d%d", &u, &v);g[u][v] = g[v][u] = 1;e[u][v] = e[v][u] = 1;ed[i] = mp(u, v);}int res = maxmatch();// printf("(%d)", res);for (int i = 1; i <= m; ++i) {int u = ed[i].fir, v = ed[i].sec;memcpy(e, g, sizeof(g));for (int j = 1; j <= n; ++j) {// 把 u, v 两点给删了e[u][j] = e[j][u] = e[v][j] = e[j][v] = 0;}if (maxmatch() != res - 1) ans.pb(i);}int si = SZ(ans);printf("%d\n", si);for (int i = 0; i < si; ++i) {printf("%d", ans[i]);if (i < si - 1) printf(" ");// printf("%d%c", ans[i], " \n"[i == si - 1]);// 这种写法如果 si == 0 时不会进循环, 所以不会有回车}puts("");}return 0;
}

『kuangbin带你飞』专题10:匹配问题相关推荐

  1. 【kuangbin带你飞】专题六 最小生成树

    [kuangbin带你飞]专题六 最小生成树 A.POJ - 1251 Jungle Roads (最小生成树模板) The Head Elder of the tropical island of ...

  2. 解题报告:【kuangbin带你飞】专题四 最短路练习题

    目录 A. POJ - 2387 TiltheCowsComeHomeTil\ the\ Cows\ Come\ HomeTil the Cows Come Home--------(最短路模板题)[ ...

  3. 解题报告:【kuangbin带你飞】专题九 连通图

    目录 A.POJ 1236 Network of Schools(有向图缩点) B.UVA 315 Network(找割点) C.UVA 796 Critical Links(桥) D.POJ 369 ...

  4. 解题报告:【kuangbin带你飞】专题十一 网络流

    目录 A.POJ 3436 ACMComputerFactoryACM\ Computer\ FactoryACM Computer Factory[省选/NOI- ] B.POJ 3281 Dini ...

  5. [kuangbin带你飞]专题十二 基础DP1 题解+总结

    kuangbin带你飞:点击进入新世界 总结: 简单dp,最近在做,持续更新. 文章目录 总结: 1.Max Sum Plus Plus 2.Ignatius and the Princess IV ...

  6. “kuangbin带你飞”专题计划——专题十四:数论基础

    写在前面 1.目前还没啥写的.开始时间:2021-05-13(其实博客上看得到该博客创建时间的) 2.上一个专题刷的是网络流(博客总结),属于第一次接触.本来想的是一周特别高效,然后一周略划水,结果是 ...

  7. (2021-07-14~)“kuangbin带你飞”专题计划——专题十三:基础计算几何

    目录 前言 参考博客 自己总结的东西: 难度判断? 题目 1.[TOYS POJ - 2318 ](解决) 2.[Toy Storage POJ - 2398 ](解决) 3.[Segments PO ...

  8. kuangbin带你飞专题合集

    题目列表 [kuangbin带你飞]专题一 简单搜索 [kuangbin带你飞]专题二 搜索进阶 [kuangbin带你飞]专题三 Dancing Links [kuangbin带你飞]专题四 最短路 ...

  9. [kuangbin带你飞]专题五 并查集 题解+总结

    kuangbin带你飞:点击进入新世界 总结: 本人算是初学者中的初学者,欢迎交流~ 并查集的接触过的不多,大概只有普通并查集,带权并查集,种族并查集,传说中的可持续化并查集只是听说过还没有接触,不过 ...

最新文章

  1. 9行代码AC——HDU 6857 -Clockwise or Counterclockwise(2020 Multi-University Training Contest 8)(判断三点顺序)
  2. [html] 实现两列等宽布局的方式有哪些?
  3. html lt p gt 标签的属性,科技常识:html中amp;lt;tableamp;gt;标签的各种属性介绍_table的使用...
  4. ssh tar_2015年最佳情侣:tar和ssh
  5. 训练效果不好的解决办法
  6. 小程序进阶学习01--功能思维导图梳理
  7. deepnode处理过的图片_微信图文排版用什么软件?文章图片大小不一样排版不齐怎么办?...
  8. 解决jquery下checked取值问题...
  9. 人工智能与深度学习概念(5)——目标检测-RCNN
  10. 挑战性题目DSCT102:木板切割问题
  11. 人工智能专业世界排名第一的大学,2022最新
  12. html打字机特效,[JS插件]酷炫的打字机效果: Typed.js
  13. 编译《视觉SLAM十四讲》ch5里joinmap出现 ***/anaconda3/lib/libpng16.so.16:‘inflateValidate@ZLIB_1.2.9’未定义的引用
  14. 名帖204 蔡襄 行书《行书帖选》
  15. 计算机毕业设计JAVA家庭饮用水监测系统mybatis+源码+调试部署+系统+数据库+lw
  16. 深度学习算法之-SSD(一)
  17. 人物照片墙html模板,制作散落照片墙效果人物照片的PS教程
  18. C语言经典题目:有5个人坐在一起,问他们分别多少岁?
  19. O32:头寸管理,我有话说
  20. 戴记严选GM3323D 鼠标左右键失效 解决办法

热门文章

  1. 207、室外远距离点对点无线网桥组网方案
  2. 心田花开:人教版一年级语文《小蝌蚪找妈妈》知识点归纳
  3. python基础练习(猜拳游戏、扎金花游戏、购物小程序)
  4. Java面试流程及核心面试题
  5. Mysql添加用户,访问指定数据库
  6. 带你快速入门MSK(一)
  7. python绘制彩色蟒蛇代码_python画彩色蟒蛇
  8. 百喝不厌的粥-介绍老家一种粥 (以后会介绍多种粥的做法)
  9. 使用Java导出数据到Excel
  10. monotonic queue 单调队列