文章目录
- 专题十 匹配问题
-
- HDU - 1045 Fire Net
- HDU 2444 The Accomodation of Students
- HDU 1083 Courses
- HDU - 1281 棋盘游戏
- HDU-2819 Swap
- HDU-2389 Rain on your Parade
- HDU - 4185 Oil Skimming
- POJ - 3020 Antenna Placement
- HDU - 1054 Strategic Game
- HDU - 1151 Air Raid
- POJ - 2594 Treasure Exploration
- HDU - 3829 Cat VS Dog
- POJ - 2289 Jamie's Contact Groups
- POJ - 2112 Optimal Milking
- POJ - 3189 Steady Cow Assignment
- HDU 2255 奔小康赚大钱
- HDU 3488 Tour
- HDU - 4687 Boke and Tsukkomi
专题十 匹配问题
HDU - 1045 Fire Net
题意: 给一个最多4*4的网格图,其中有些地方障碍,现在在无障碍的格子放棋子,各个棋子不能同行同列,问最多可以放多少个棋上去。
题解: 将每一行借助障碍点划分为若干个分段,每个分段缩为一个点。对行做这个操作,得到n1个行的点;对列做这个操作,得到n2个列的点。如果列的点和行的点有交叉,那么就把行的点向列的点连一条边。然后问题就变为了最大匹配问题,跑匈牙利算法即可。
![微信截图_20201214233232.png](https://i.loli.net/2020/12/14/ESgXIATnzKs132D.png)
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
int const N = 10;
int n, m, T, n1, n2, belong[N][N], g[20 * 2][20 * 2], st[20], match[20];
string s[N];
bool find(int x) {
for (int i = 1; i <= n2; ++i) {
if (!g[x][i]) continue;
if (!st[i]) {
st[i] = 1;
if (!match[i] || find(match[i])) {
match[i] = x;
return true;
}
}
}
return false;
}
int main() {
while(scanf("%d", &n)!= EOF && n) {
n1 = 0, n2 = 0;
memset(g, 0, sizeof g);
memset(match, 0, sizeof match);
memset(belong, 0, sizeof belong);
for (int i = 0; i < n; ++i) cin >> s[i];
for (int k = 0; k < n; ++k) {
for (int i = 0, j = 0; i < n; i = j) {
// 按照障碍点分段
j = i;
while(j < n && s[k][i] == s[k][j]) j++;
if (s[k][i] == 'X') continue;
n1++;
for (int idx = i; idx < j; ++idx) belong[k][idx] = n1; // 将每个缩点给个标号
}
}
for (int k = 0; k < n; ++k) {
for (int i = 0, j = 0; i < n; i = j) {
j = i;
while(j < n && s[i][k] == s[j][k]) j++;
if (s[i][k] == 'X') continue;
n2++;
for (int idx = i; idx < j; idx++) {
g[belong[idx][k]][n2] = 1;
}
}
}
int res = 0;
for (int i = 1; i <= n1; ++i) {
memset(st, 0, sizeof st);
if (find(i)) res++;
}
cout << res << endl;
}
return 0;
}
HDU 2444 The Accomodation of Students
题意: 将所有人分成两组,每组里不能有相互认识的同学,如果不能分成这样的组输出No,
如果能,则朋友之间相互两两配对,输出最大可配对数;
题解: 模板题
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
int const MAXN = 200 + 10, MAXM = MAXN * MAXN * 2;
int n, m, T;
int e[MAXM], ne[MAXM], idx, h[MAXN], color[MAXN], st[MAXN], match[MAXN];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int dfs(int u, int c) {
color[u] = c; // 给u号点打上c的颜色
for (int i = h[u]; i != -1; i = ne[i]) {
// 遍历所有与i号点相连的点
int j = e[i]; // 看j号点
if (!color[j]) {
// 如果j号点没有染色
if (!dfs(j, 3 - c)) return 0; // 如果j号点染色3-c过程中失败
}
if (color[j] == c) return 0; // 如果j号点也染色c颜色
}
return 1; // 如果u号点的所有邻点都没有染色失败
}
bool find(int x) {
for (int i = h[x]; i != -1; i = ne[i]) {
// 遍历所有与x之间相连的点
int j = e[i]; // 点为j
if (!st[j]) {
// 如果j没有匹配过
st[j] = 1; // 记录j匹配过
if (!match[j] || find(match[j])) {
// 如果右半部分的j没有匹配到左半部分的人或者j匹配到的可以去匹配其他右半部分的人
match[j] = x; // 记录右半部分的j和左半部分的x匹配
return true; // 找到x的匹配对象
}
}
}
return false; // 全部都遍历仍然没有成功,返回false
}
int main() {
while(scanf("%d%d", &n, &m) != EOF) {
memset(h, -1, sizeof h);
memset(match, 0, sizeof match);
memset(color, 0, sizeof color);
idx = 0;
for (int i = 1, a, b; i <= m; ++i) {
scanf("%d%d", &a, &b);
add(a, b), add(b, a); // 可以建2条边,最后答案除以2即可
}
int flg = 1; // flg记录染色是否成功,成功为1,失败为0
for (int i = 1; i <= n; ++i) {
// 从1号点开始枚举
if (!color[i]) {
// 如果i号点为染色
if (!dfs(i, 1)) {
// 如果i号点染色失败
flg = 0; // flg 打上失败标记
break;
}
}
}
if (!flg) {
puts("No");
continue;
}
int res = 0;
for (int i = 1; i <= n; ++i) {
// 每个点都搜索一次增广路,然后答案除以2即可
memset(st, 0, sizeof st);
if (find(i)) res++;
}
printf("%d\n", res / 2);
}
return 0;
}
HDU 1083 Courses
题意: 求最大匹配
题解: 水题
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
int const MAXN = 1000, MAXM = 3e5 + 10;
int n, m, T;
int e[MAXM], ne[MAXM], idx, h[MAXN], st[MAXN], match[MAXN];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool find(int x) {
for (int i = h[x]; i != -1; i = ne[i]) {
// 遍历所有与x之间相连的点
int j = e[i]; // 点为j
if (!st[j]) {
// 如果j没有匹配过
st[j] = 1; // 记录j匹配过
if (!match[j] || find(match[j])) {
// 如果右半部分的j没有匹配到左半部分的人或者j匹配到的可以去匹配其他右半部分的人
match[j] = x; // 记录右半部分的j和左半部分的x匹配
return true; // 找到x的匹配对象
}
}
}
return false; // 全部都遍历仍然没有成功,返回false
}
int main() {
cin >> T;
while(T--) {
memset(match, 0, sizeof match);
memset(h, -1, sizeof h);
idx = 0;
scanf("%d%d", &m, &n);
for (int i = 1; i <= m; ++i) {
int t;
scanf("%d", &t);
for (int j = 1; j <= t; ++j) {
int s;
scanf("%d", &s);
add(i, s);
}
}
int res = 0;
for (int i = 1; i <= m; ++i) {
memset(st, 0, sizeof st);
if (find(i)) res++;
}
if (res == m) printf("YES\n");
else printf("NO\n");
}
return 0;
}
HDU - 1281 棋盘游戏
题意: n*m的棋盘,其中只有k个位置可以放棋子。同一行或者同一列不能放“车”:
1.问最多可以放多少个棋子
2.问在第1问的基础上,有多少个棋子的位置是不能改变的,如果改变第1问的结果就会改变
![Snipaste_2020-12-24_02-19-22.png](https://i.loli.net/2020/12/24/zQwUPiOXa5BhMGy.png)
题解: 每一行每一列只能放一辆车,因此可以把行和列分别当成左部图和右部图,那么可以放的点(x, y)就可以把对应的行和列连一条边,然后跑最大匹配即可知道最多多少车。第二问问关键点个数,那么可以把每个可以放的点删去,也就是删去一条边。然后再次跑最大匹配,看最大匹配十分和第一问一样,不一样则是关键点。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
int const MAXN = 1e2 + 10;
int n, m, T, k, kase= 1;
int g[MAXN][MAXN], st[MAXN], match[MAXN];
PII pos[MAXN * MAXN];
int find(int x) {
for (int i = 1; i <= n; ++i) {
if (!g[x][i]) continue;
if (!st[i]) {
st[i] = 1;
if (!match[i] || find(match[i])) {
match[i] = x;
return true;
}
}
}
return false;
}
int hungry() {
int res = 0;
for (int i = 1; i <= n; ++i) {
memset(st, 0, sizeof st);
if (find(i)) res++;
}
return res;
}
int main() {
while(scanf("%d%d%d", &n, &m, &k) != EOF) {
for (int i = 1; i <= n; ++i) {
st[i] = match[i] = 0;
}
memset(g, 0, sizeof g);
for (int i = 1; i <= k; ++i) {
scanf("%d%d", &pos[i].first, &pos[i].second);
g[pos[i].first][pos[i].second] = 1;
}
int max_match = hungry();
int importance = 0;
for (int i = 1; i <= k; ++i) {
g[pos[i].first][pos[i].second] = 0;
memset(match, 0, sizeof match);
if (hungry() != max_match) importance++;
g[pos[i].first][pos[i].second] = 1;
}
printf("Board %d have %d important blanks for %d chessmen.\n", kase++, importance, max_match);
}
return 0;
}
HDU-2819 Swap
题意: 交换图的某些行或者是某些列(可以都换),使得这个N*N的图对角线上全部都是1.
题解: 如果通过交换某些行没有办法的到解的话,那么只交换列 或者 既交换行又交换列 那也没办法得到解,因此只交换行或者列即可。对于 g [ i ] [ j ] = 1 g[i][j] = 1 g[i][j]=1,那么为了使得对角线变为1即 g [ i ] [ i ] = 1 g[i][i]=1 g[i][i]=1,那么交换第i行和第j列,一旦 g [ i ] [ j ] = 1 g[i][j]=1 g[i][j]=1,表明存在一条边从 i − > j i->j i−>j,依次建图,跑二分匹配,如果匹配数目不为n,说明不存在交换的方式。打印路径的话,因为每个点都是一一对应的,所以找到每列要交换的列,然后swap即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
int const N = 1e2 + 10;
int n, m, T;
int g[N][N], st[N], match[N];
int ans_l[N], ans_r[N];
bool