目录
- 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) {// 交替爬树找 lca
for (++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;
}