文章目录
- Fire Net ——HDU 1045
- The Accomodation of Students——HDU 2444
- Courses——HDU 1083
- 棋盘游戏——HDU 1281
- Swap——HDU 2819
- Rain on your Parade——HDU 2389
- Oil Skimming——HDU 4185
- Antenna Placement——POJ 3020
- Strategic Game
- Air Raid——HDU 1151
- Treasure Exploration——POJ 2594
- Cat VS Dog——HDU 3829
- Jamie's Contact Groups--POJ - 2289
- Optimal Milking--POJ - 2112
- Steady Cow Assignment--POJ - 3189
- 奔小康赚大钱--HDU - 2255
- Tour--HDU - 3488
- Work Scheduling--URAL - 1099
- Boke and Tsukkomi - HDU - 4687
1.匈牙利算法,匹配,最大匹配,匹配点,增广路径
最大匹配<=>不存在增广路径
2.最小点覆盖,最大独立集,最小路径覆盖(最小路径重复点覆盖)
最大匹配数=最小点覆盖=总点数-最大独立集=总点数-最小路径覆盖
最大独立集:选出最多的点,使得选出的点之间没有边
在二分图中,求最大独立集 n-m<=>去掉最少的点,将所有边都破坏掉 m<=>找最小点覆盖 m<=>找最大匹配 m
最小路径重复点覆盖:求传递闭包G` 在G`求最小路径覆盖
最小路径覆盖DAG(有向无环图)用最少的互不相交的路径,将所有点覆盖
最小路径重复点覆盖:取消对路径互不相交的约束(求出最少的路径将所有点覆盖至少一次)
Fire Net ——HDU 1045
大致题意:
匈牙利求最大匹配数
解题思路:
一行或一列只能同时放一个大炮,因此我们可以将行和列分成两部分,把行上的点放在左边,列上的点放在右边
对于有X的行或列,两边可以放不同的点,最后将行和列连线,求最大匹配数即可
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 10;
int n;
char mp[N][N];
int g[N][N];
int vis[N], match[N];
int cnt1, cnt2;
int a[N][N], b[N][N];
bool find(int u) {
for (int v = 1; v <= cnt2; ++v) {
if (!vis[v] && g[u][v]) {
vis[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main(void)
{
while (cin >> n, n) {
memset(match, -1, sizeof match);
memset(a, 0, sizeof a);
memset(b, 0, sizeof b);
memset(g, 0, sizeof g);
for (int i = 1; i <= n; ++i)cin >> mp[i] + 1;
cnt1 = cnt2 = 0;
for (int i = 1; i <= n; ++i) { //行
cnt1++;
for (int j = 1; j <= n; ++j) {
if (mp[i][j] == 'X' && j != 1 && j != n)cnt1++;
else a[i][j] = cnt1;
}
}
for (int j = 1; j <= n; ++j) { //列
cnt2++;
for (int i = 1; i <= n; ++i) {
if (mp[i][j] == 'X' && i != 1 && i != n)cnt2++;
else b[i][j] = cnt2;
}
}
//将行与列连线,建二分图
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
if (mp[i][j] == 'X')continue;
g[a[i][j]][b[i][j]] = 1;
}
//匈牙利算法
int res = 0;
for (int i = 1; i <= cnt1; ++i) {
memset(vis, 0, sizeof vis);
if (find(i))res++;
}
cout << res << endl;
}
return 0;
}
The Accomodation of Students——HDU 2444
大致题意:
染色法判断二分图+匈牙利求最大匹配数
解题思路:
将n个人分成两组,认识的两个人之间连线,用染色法判断是不是二分图即可
组内的人相互不认识,求最多有多少小队互相认识,就是匈牙利求最大匹配
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 210;
int n, m;
int color[N];
int vis[N], match[N];
vector<int>e[N];
bool dfs(int u, int c) {
color[u] = c;
for (auto& v : e[u]) {
if (color[v]) {
if (color[v] == c)return false;
}
else if (!dfs(v, 3 - c))return false;
}
return true;
}
bool check() {
memset(color, 0, sizeof color);
for (int i = 1; i <= n; ++i)
if (!color[i])
if (!dfs(i, 1))return false;
return true;
}
bool find(int u) {
for (auto& v : e[u]) {
if (!vis[v]) {
vis[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main() {
while (cin >> n >> m) {
for (int i = 0; i <= n; ++i)e[i].clear();
memset(match, -1, sizeof match);
while (m--) {
int a, b; cin >> a >> b;
e[a].push_back(b); e[b].push_back(a);//无向图
}
if (!check()) { //染色法判断二分图
puts("No");
continue;
}
else {//匈牙利求最大匹配
int res = 0;
for (int i = 1; i <= n; ++i) {
if (color[i] == 1) { //不加判断,最后答案/2
memset(vis, 0, sizeof vis);
if (find(i))res++;
}
}
cout << res << endl;
}
}
return 0;
}
Courses——HDU 1083
大致题意:
裸匈牙利算法
解题思路:
左边放学生,右边放课程,学生与课程连线,看匈牙利最大匹配数是否与课程数相等
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 310;
int n, m;
int vis[N], match[N];
vector<int>e[N];
bool find(int u) {
for (auto& v : e[u]) {
if (!vis[v]) {
vis[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main() {
int t; cin >> t;
while (t--) {
cin >> m >> n; //注意输入顺序
for (int i = 0; i <= n; ++i)e[i].clear();
memset(match, -1, sizeof match);
for (int u = 1; u <= m; ++u) {
int cnt; cin >> cnt;
while (cnt--) {
int v; cin >> v;
e[u].push_back(v);
}
}
int res = 0;
for (int i = 1; i <= n; ++i) {
memset(vis, 0, sizeof vis);
if (find(i))res++;
}
printf("%s\n", res == m ? "YES" : "NO");
}
return 0;
}
棋盘游戏——HDU 1281
大致题意:
求匈牙利最大匹配出的边有多少边是可以替换的
解题思路:
根据行和列建二分图,每一个点有x,y,连一条x向y的边,构建二分图
先记录下来最大匹配数,然年枚举匈牙利里的边,如果删去该边,对求出的最大匹配数有影响,代表其不能替换,统计没有影响的边即可
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 310;
int n, m, k;
int g[N][N];
int vis[N], match[N];
int mat[N];
bool find(int u) {
for (int v = 1; v <= m; ++v) {
if (!vis[v] && g[u][v]) {
vis[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int getnum() {
int res = 0;
memset(match, -1, sizeof match);
for (int i = 1; i <= n; ++i) {
memset(vis, 0, sizeof vis);
if (find(i))res++;
}
return res;
}
int main() {
int t = 1;
while (cin >> n >> m >> k) {
memset(g, 0, sizeof g);
while (k--) {
int a, b; cin >> a >> b;
g[a][b] = 1;
}
int res = getnum(); //记录最大匹配数
memcpy(mat, match, sizeof match);
int cnt = 0;
for (int i = 1; i <= n; ++i) {
if (mat[i] == -1)continue;
g[mat[i]][i] = 0; //删边
int num = getnum();
if (res != num)cnt++;
g[mat[i]][i] = 1; //恢复状态
}
printf("Board %d have %d important blanks for %d chessmen.\n", t++, cnt, res);
}
return 0;
}
Swap——HDU 2819
大致题意:
通过交换行和列使主对角线全为1
解题思路:
1 将x与y连线,左边放行,右边放列,构建二分图
2 -1的情况只需要判断最大匹配数是否等于n即可,这里可以用矩阵的秩来证明:对角线为1,说明矩阵的秩为n,任意交换行或者列矩阵的秩不改变,所以只有最大匹配数等于n的时候才会有解
3 这时我们就要考虑交换行还是交换列,还是既交换行也交换列,其实我们只需要选一个就可以,如果只交换列无解,那么其他方法也无解
4 g[i][j]表示i到j有单向边,可以理解为第i列与第j列交换,使的第i行第i列的位置为1,如果i!=match[i],我们就进行交换,直到(i,i)变为1
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int g[N][N];
int vis[N], match[N];
vector<pair<int, int>>ans;
bool find(int u) {
for (int v = 1; v <= n; ++v) {
if (!vis[v] && g[u][v]) {
vis[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main() {
while (cin >> n) {
memset(match, -1, sizeof match);
memset(g, 0, sizeof g);
ans.clear();
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
cin >> g[i][j];
int res = 0;
for (int i = 1; i <= n; ++i) {
memset(vis, 0, sizeof vis);
if (find(i))res++;
}
if (res != n) {
cout << -1 << endl; continue;
}
for (int i = 1; i <= n; ++i) {
while (i != match[i]) {
ans.push_back({ i,match[i] });
swap(match[i], match[match[i]]);
}
}
cout << ans.size() << endl;
for (int i = 0; i <ans.size(); ++i)
printf("C %d %d\n", ans[i].first, ans[i].second);
}
return 0;
}
Rain on your Parade——HDU 2389
大致题意:
k分钟后下雨,给出n个人的位置和奔跑速度,以及m把雨伞的位置.(一个人只能用一把伞),问最多有多少人可以不淋雨
解题思路:
首先我们考虑建二分图,我们可以将人与伞连线,但不是所有人都连线,我们将在雨来之前可以奔跑到雨伞位置的人连线,建成二分图
左边是人,右边是雨伞,求最大匹配m
匈牙利算法时间复杂度是O(n*m),会超时,所以我们要用HK算法,时间复杂度是O(logn*m)
有两种建图的方式,取决于点的编号,第一个代码是左右两边混用编号,第二个代码是左边一个编号,右边一个编号
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 6050;
int n, m, k;
int match[N], depth[N];
vector<int>e[N];
int x[N], y[N], s[N];
bool bfs() {
bool flag = false;
memset(depth, 0, sizeof depth);
queue<int>q;
for (int i = 1; i <= n; ++i)
if (match[i] == -1)q.push(i);
while (!q.empty()) {
int u = q.front(); q.pop();
for (auto& v : e[u]) {
if (!depth[v]) {
depth[v] = depth[u] + 1;
if (match[v] == -1)flag = true;
else {
depth[match[v]] = depth[v] + 1;
q.push(match[v]);
}
}
}
}
return flag;
}
bool find(int u) {
for (auto& v : e[u]) {
if (depth[v] != depth[u] + 1)continue;
depth[v] = 0;
if (match[v] == -1 || find(match[v])) {
match[v] = u; match[u] = v;
return true;
}
}
return false;
}
int main(void)
{
int t; cin >> t;
for (int T = 1; T <= t; ++T) {
memset(match, -1, sizeof match);
cin >> k >> n;
for (int i = 0; i <= n; ++i)e[i].clear();
for (int i = 1; i <= n; ++i)cin >> x[i] >> y[i] >> s[i];
cin >> m;
for (int i = 1; i <= m; ++i)cin >> x[i + n] >> y[i + n];
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) {
int u = i, v = j + n;
int dis = (x[u] - x[v]) * (x[u] - x[v]) + (y[u] - y[v]) * (y[u] - y[v]);
int tmp = k * s[u]; tmp *= tmp;
if (dis <= tmp)e[u].push_back(v);
}
int res = 0;
while (bfs()) {
for (int i = 1; i <= n; ++i)
if (match[i] == -1 && find(i))res++;
}
printf("Scenario #%d:\n%d\n\n", T, res);
}
return 0;
}
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 3e3 + 10, M = N * N;
int n, m, k;
struct node {
int x, y, s;
}a[N], b[N];
vector<int>e[N];
int vis[N], depth[N];
int con[N];
int matchx[N], matchy[N], dx[N], dy[N];
int dis;
bool bfs() {
memset(dx, -1, sizeof dx);
memset(dy, -1, sizeof dy);
queue<int>q;
for (int i = 1; i <= n; ++i)
if (matchx[i] == -1)q.push(i);
dis = INF;
while (!q.empty()) {
int u = q.front(); q.pop();
if (dx[u] > dis)break;
for (auto& v : e[u]) {
if (dy[v] == -1) {
dy[v] = dx[u] + 1;
if (matchy[v] == -1)dis = dy[v];
else {
dx[matchx[v]] = dy[v] + 1;
q.push(matchy[v]);
}
}
}
}
return dis != INF;
}
bool dfs(int u) {
for (auto& v : e[u]) {
if (vis[v] || dy[v] != dx[u] + 1)continue;
vis[v] = 1;
if (matchy[v] != -1 && dy[v] == dis)continue;
if (matchy[v] == -1 || dfs(matchy[v])) {
matchy[v] = u; matchx[u] = v;
return true;
}
}
return false;
}
int solve() {
int res = 0;
memset(matchx, -1, sizeof matchx);
memset(matchy, -1, sizeof matchy);
while (bfs()) {
memset(vis, 0, sizeof vis);
for (int i = 1; i <= n; ++i)
if (matchx[i] == -1 && dfs(i))res++;
}
return res;
}
int main(void)
{
int t; scanf("%d", &t);
for (int T = 1; T <= t; ++T) {
scanf("%d%d", &k, &n);
for (int i = 0; i <= n; ++i)e[i].clear();
for (int i = 1; i <= n; ++i)
scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].s);
scanf("%d", &m);
for (int i = 1; i <= m; ++i)scanf("%d%d", &b[i].x, &b[i].y);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) {
int x = a[i].x - b[j].x, y = a[i].y - b[j].y;
int dis = x * x + y * y;
int w = k * a[i].s; w *= w;
if (dis <= w)e[i].push_back(j);
}
int res = solve();
printf("Scenario #%d:\n%d\n\n", T, res);
}
return 0;
}
Oil Skimming——HDU 4185
大致题意:
给出n*n的图,’#‘代表石油,’.'代表水,只能用1*2的矩形,且完全覆盖,问最多可以用多少个矩形收集石油.(矩形之间不能覆盖)
解题思路:
如何建图?
我们可以根据坐标和的奇偶分成两部分,将左右连线,求最大匹配数
上面建出的二分图是有向图,也可以将油田之间全部连线,最后答案/2即可
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 610;
int n;
char mp[N][N];
int g[N][N];
int vis[N], match[N];
int id[N][N], cnt1, cnt2;
int fx[][2] = { 0,1,0,-1,1,0,-1,0 };
bool find(int u) {
for (int v = 1; v <= cnt2; ++v) {
if (!vis[v] && g[u][v]) {
vis[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main(void)
{
int t; cin >> t;
for (int T = 1; T <= t; ++T) {
cnt1 = cnt2 = 0;
memset(g, 0, sizeof g);
memset(id, 0, sizeof id);
memset(match, -1, sizeof match);
cin >> n;
for (int i = 1; i <= n; ++i)cin >> mp[i] + 1;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
if (mp[i][j] == '.')continue;
if ((i + j) & 1)id[i][j] = ++cnt2;
else id[i][j] = ++cnt1;
}
//建二分图
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
if (mp[i][j] == '.' || (i + j) & 1)continue;
for (int k = 0; k < 4; ++k) {
int x = i + fx[k][0], y = j + fx[k][1];
if (x <= 0 || x > n || y <= 0 || y > n || mp[x][y] == '.')continue;
g[id[i][j]][id[x][y]] = 1;
}
}
//匈牙利
int res = 0;
for (int i = 1; i <= cnt1; ++i) {
memset(vis, 0, sizeof vis);
if (find(i))res++;
}
printf("Case %d: %d\n", T, res);
}
return 0;
}
Antenna Placement——POJ 3020
大致题意:
跟上个题题意一样,只不过这题1*2的矩形可以相互覆盖
求把所有’*'盖住的最少矩形
解题思路:
最小路径覆盖=总点数-最大匹配数
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 610;
int n, m;
char mp[N][N];
int g[N][N];
int vis[N], match[N];
int id[N][N], cnt1, cnt2;
int fx[][2] = { 0,1,0,-1,1,0,-1,0 };
bool find(int u) {
for (int v = 1; v <= cnt2; ++v) {
if (!vis[v] && g[u][v]) {
vis[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main(void)
{
int t; cin >> t;
while (t--) {
cnt1 = cnt2 = 0;
memset(g, 0, sizeof g);
memset(id, 0, sizeof id);
memset(match, -1, sizeof match);
cin >> n >> m;
for (int i = 1; i <= n; ++i)cin >> mp[i] + 1;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) {
if (mp[i][j] == 'o')continue;
if ((i + j) & 1)id[i][j] = ++cnt2;
else id[i][j] = ++cnt1;
}
//建二分图
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) {
if (mp[i][j] == 'o' || (i + j) & 1)continue;
for (int k = 0; k < 4; ++k) {
int x = i + fx[k][0], y = j + fx[k][1];
if (x <= 0 || x > n || y <= 0 || y > m || mp[x][y] == 'o')continue;
g[id[i][j]][id[x][y]] = 1;
}
}
//匈牙利
int res = 0;
for (int i = 1; i <= cnt1; ++i) {
memset(vis, 0, sizeof vis);
if (find(i))res++;
}
//最小路径覆盖 = 总点数 - 最大匹配数
printf("%d\n", cnt1 + cnt2 - res);
}
return 0;
}
Strategic Game
大致题意:
最小点覆盖是用最少的点包含所有边
解题思路:
最小点覆盖=最大匹配数
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1510;
int n, m;
int vis[N];
int match[N];
vector<int>e[N];
bool find(int u) {
for (auto& v : e[u]) {
if (!vis[v]) {
vis[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main(void)
{
while (cin >> n) {
memset(match, -1, sizeof match);
for (int i = 0; i <= n; ++i)e[i].clear();
for (int i = 0; i < n; ++i) {
int a, b;
scanf("%d:(%d)", &a, &m);
while (m--) {
scanf("%d", &b);
e[a].push_back(b); e[b].push_back(a);
}
}
int res = 0;
for (int i = 0; i < n; ++i) {
memset(vis, 0, sizeof vis);
if (find(i))res++;
}
//无向图 答案/2
printf("%d\n", res / 2);
}
return 0;
}
Air Raid——HDU 1151
大致题意:
求出最少的路径将所有点覆盖至少一次
解题思路:
最小路径重复点覆盖:求传递闭包G 在G上求最小路径覆盖,最小路径覆盖=总点数-最大匹配
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 610;
int n, m;
int g[N][N];
int vis[N];
int match[N];
bool find(int u) {
for (int v = 1; v <= n; ++v) {
if (!vis[v] && g[u][v]) {
vis[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main(void)
{
int t; cin >> t;
while (t--) {
memset(match, -1, sizeof match);
memset(g, 0, sizeof g);
cin >> n >> m;
while (m--) {
int a, b; cin >> a >> b;
g[a][b] = 1;
}
//传递闭包
for (int k = 1; k <= n; ++k)
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
g[i][j] |= g[i][k] && g[k][j];
int res = 0;
for (int i = 1; i <= n; ++i) {
memset(vis, 0, sizeof vis);
if (find(i))res++;
}
//最小路径覆盖=总点数-最大匹配
printf("%d\n", n - res);
}
return 0;
}
Treasure Exploration——POJ 2594
大致题意:
让士兵走最少的路径将所有点覆盖至少一次
跟上一题一样
解题思路:
最小路径重复点覆盖:求传递闭包G 在G上求最小路径覆盖,最小路径覆盖=总点数-最大匹配
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 610;
int n, m;
int g[N][N];
int vis[N];
int match[N];
bool find(int u) {
for (int v = 1; v <= n; ++v) {
if (!vis[v] && g[u][v]) {
vis[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main(void)
{
while (cin >> n >> m, n + m) {
memset(match, -1, sizeof match);
memset(g, 0, sizeof g);
while (m--) {
int a, b; cin >> a >> b;
g[a][b] = 1;
}
for (int k = 1; k <= n; ++k)
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
g[i][j] |= g[i][k] && g[k][j];
int res = 0;
for (int i = 1; i <= n; ++i) {
memset(vis, 0, sizeof vis);
if (find(i))res++;
}
printf("%d\n", n - res);
}
return 0;
}
Cat VS Dog——HDU 3829
大致题意:
如果一个孩子喜欢和另一个孩子不喜欢的动物相同,连线
最大独立集:选出最多的点,使得选出的点之间没有边
解题思路:
最大独立集=总点数-最大匹配数
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 610;
int n, m, k;
int g[N][N];
int vis[N];
int match[N];
string like[N], dislike[N];
bool find(int u) {
for (int v = 1; v <= n; ++v) {
if (!vis[v] && g[u][v]) {
vis[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main(void)
{
while (cin >> m >> k >> n) {
memset(match, -1, sizeof match);
memset(g, 0, sizeof g);
for (int i = 1; i <= n; ++i)cin >> like[i] >> dislike[i];
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if (like[i] == dislike[j] || dislike[i] == like[j])g[i][j] = 1;
int res = 0;
for (int i = 1; i <= n; ++i) {
memset(vis, 0, sizeof vis);
if (find(i))res++;
}
//无向图 最大匹配数/2
printf("%d\n", n - res / 2);
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
const int N = 610;
int n, m, k;
int g[N][N];
int vis[N];
int match[N];
string like[N], dislike[N];
bool find(int u) {
for (int v = 1; v <= n; ++v) {
if (!vis[v] && g[u][v]) {
vis[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int main(void)
{
while (cin >> m >> k >> n) {
memset(match, -1, sizeof match);
memset(g, 0, sizeof g);
for (int i = 1; i <= n; ++i)cin >> like[i] >> dislike[i];
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if (like[i] == dislike[j] || dislike[i] == like[j])g[i][j] = 1;
int res = 0;
for (int i = 1; i <= n; ++i) {
memset(vis, 0, sizeof vis);
if (find(i))res++;
}
//无向图 最大匹配数/2
printf("%d\n", n - res / 2);
}
return 0;
}
Jamie’s Contact Groups–POJ - 2289
大致题意:
给出n个人m个分组以及每个人可以在那些组中,求每个组的上限最小,求出上限值
解题思路:
二分+多重匹配
我们可以将人放左边,分组放右边,显然右边可以匹配左边很多人
答案符合单调性,我们可以二分答案,看当前上限mid的情况下是否可以完成匹配
AC代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
const int N = 1010;
int n, m;
int g[N][N], cnt[N];
int mp[N][N];
int vis[N];
bool find(int u, int mid) {
for (int v = 0; v < m; ++v) {
if (!vis[v] && mp[u][v]) {
vis[v] = 1;
if (cnt[v] < mid) {
g[v][cnt[v]++] = u;
return true;
}
for (int j = 0; j < cnt[v]; ++j) {
if (find(g[v][j], mid)) {
g[v][j] = u;
return true;
}
}
}
}
return false;
}
bool check(int mid) {
memset(cnt, 0, sizeof cnt);
for (int i = 0; i < n; ++i) {
memset(vis, 0, sizeof vis);
if (!find(i, mid))return false;
}
return true;
}
int main() {
while (cin >> n >> m, n + m) {
memset(g, 0, sizeof g);
memset(mp, 0, sizeof mp);
for (int i = 0; i < n; ++i) {
char s[N]; scanf("%s", &s);
while (1) {
int x; scanf("%d", &x);
mp[i][x] = 1;
char ch = getchar();
if (ch == '\n')
break;
}
}
int l = 0, r = n;
while (l < r) {
int mid = l + r >> 1;
if (check(mid))r = mid;
else l = mid + 1;
}
cout << l << endl;
}
return 0;
}
Optimal Milking–POJ - 2112
大致题意:
k个机器,c头奶牛,每台机器最多只能给m个买牛挤奶,求奶牛到机器的最大距离最小
解题思路:
左边是机器,右边是奶牛,右边可以匹配左边的多个机器,建图
先跑一边floyd,预处理出奶牛到机器的最短距离
然后二分看是否匹配
AC代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1010;
int n, m, p;
int g[N][N], cnt[N];
int mp[N][N];
int vis[N];
bool find(int u, int mid) {
for (int v = 1; v <= n; ++v) {
if (!vis[v] && mp[u][v] <= mid) {
vis[v] = 1;
if (cnt[v] < p) {
g[v][cnt[v]++] = u;
return true;
}
for (int j = 0; j < cnt[v]; ++j) {
if (find(g[v][j], mid)) {
g[v][j] = u;
return true;
}
}
}
}
return false;
}
bool check(int mid) {
memset(cnt, 0, sizeof cnt);
for (int i = n + 1; i <= n + m; ++i) {
memset(vis, 0, sizeof vis);
if (!find(i, mid))return false;
}
return true;
}
int main() {
while (cin >> n >> m >> p) {
memset(mp, INF, sizeof mp);
memset(g, 0, sizeof g);
for (int i = 1; i <= n + m; ++i)
for (int j = 1; j <= n + m; ++j) {
cin >> mp[i][j];
if (!mp[i][j])mp[i][j] = INF;
}
//floyd
for (int k = 1; k <= n + m; ++k)
for (int i = 1; i <= n + m; ++i)
for (int j = 1; j <= n + m; ++j)
mp[i][j] = min(mp[i][j], mp[i][k] + mp[k][j]);
int l = 0, r = INF;
while (l < r) {
int mid = l + r >> 1;
if (check(mid))r = mid;
else l = mid + 1;
}
cout << l << endl;
}
return 0;
}
Steady Cow Assignment–POJ - 3189
大致题意:
n头牛m个牛棚,每个牛棚容纳牛的上限是c[i],每个牛对每个牛棚都有一个喜爱值,从1到m,安排这些牛使得牛的最大喜爱值和最小喜爱值的差最小
解题思路:
我们可以二分最大喜爱值和最小喜爱值的差,然后枚举值的区间,只要有一个区间满足,说明这个答案就满足
AC代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1010;
int n, m;
int g[N][N], cnt[N];
int mp[N][N];
int vis[N];
int c[N];
bool find(int u, int s, int e) {
for (int i = s; i <= e; ++i) {
int v = mp[u][i]; //牛棚
if (!vis[v]) {
vis[v] = 1;
if (cnt[v] < c[v]) {
g[v][cnt[v]++] = u;
return true;
}
for (int j = 0; j < cnt[v]; ++j) {
if (find(g[v][j], s, e)) {
g[v][j] = u;
return true;
}
}
}
}
return false;
}
bool check(int mid) {
for (int j = 1; j <= m; ++j) {
bool flag = true;
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= n; ++i) { //牛
memset(vis, 0, sizeof vis);
if (!find(i, j, j + mid - 1)) {
flag = false;
break;
}
}
if (flag)return true;
}
return false;
}
int main() {
while (cin >> n >> m) {
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
cin >> mp[i][j];
for (int i = 1; i <= m; ++i)cin >> c[i];
int l = 0, r = m;
while (l < r) {
int mid = l + r >> 1;
if (check(mid))r = mid;
else l = mid + 1;
}
cout << l << endl;
}
return 0;
}
奔小康赚大钱–HDU - 2255
大致题意:
解题思路:
KM模板题
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 310;
int n;
int g[N][N], match[N];
int visx[N], visy[N];
int wx[N], wy[N];
int minsub;
bool find(int u) {
visx[u] = 1;
for (int v = 1; v <= n; ++v) {
if (visy[v])continue;
int tmp = wx[u] + wy[v] - g[u][v];
if (!tmp) {
visy[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
else if (tmp > 0)minsub = min(minsub, tmp);
}
return false;
}
int km() {
for (int i = 1; i <= n; ++i) {
while (true) {
memset(visx, 0, sizeof visx);
memset(visy, 0, sizeof visy);
minsub = INF;
if (find(i))break;
for (int j = 1; j <= n; ++j) {
if (visx[j])wx[j] -= minsub;
if (visy[j])wy[j] += minsub;
}
}
}
int res = 0;
for (int i = 1; i <= n; ++i)
if (match[i])res += g[match[i]][i];
return res;
}
int main(void)
{
while (~scanf("%d", &n)) {
memset(match, -1, sizeof match);
memset(wx, 0, sizeof wx);
memset(wy, 0, sizeof wy);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
scanf("%d", &g[i][j]);
wx[i] = max(wx[i], g[i][j]);
}
printf("%d\n", km());
}
return 0;
}
Tour–HDU - 3488
大致题意:
给你一个有向图,边有权值,现在要你求若干个环包含所有的顶点,并且每个顶点只出现一次(除了一个环中的起始点)使得环中所有边得权值之和最小
解题思路:
KM算法是求最大权值,本题是求最小权值,我们可以将权值取负,负值的最大就是正值的最小
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 310;
int n, m;
int g[N][N], match[N];
int visx[N], visy[N];
int wx[N], wy[N];
int minsub;
bool find(int u) {
visx[u] = 1;
for (int v = 1; v <= n; ++v) {
if (visy[v])continue;
int tmp = wx[u] + wy[v] - g[u][v];
if (!tmp) {
visy[v] = 1;
if (match[v] == -1 || find(match[v])) {
match[v] = u;
return true;
}
}
else if (tmp > 0)minsub = min(minsub, tmp);
}
return false;
}
int km() {
for (int i = 1; i <= n; ++i) {
while (true) {
memset(visx, 0, sizeof visx);
memset(visy, 0, sizeof visy);
minsub = INF;
if (find(i))break;
for (int j = 1; j <= n; ++j) {
if (visx[j])wx[j] -= minsub;
if (visy[j])wy[j] += minsub;
}
}
}
int res = 0;
for (int i = 1; i <= n; ++i)
if (match[i])res += g[match[i]][i];
return -res;
}
int main(void)
{
int t; scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
memset(match, -1, sizeof match);
memset(wx, 0, sizeof wx);
memset(wy, 0, sizeof wy);
memset(g, -INF, sizeof g);
while (m--) {
int a, b, c; scanf("%d%d%d", &a, &b, &c);
g[a][b] = max(-c, g[a][b]);
}
printf("%d\n", km());
}
return 0;
}
Work Scheduling–URAL - 1099
大致题意:
一般图求最大匹配
解题思路:
一般图带花树模板题
AC代码:
//时间复杂度O(n^3)
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 300;
int n;
int g[N][N], match[N];
int pre[N];
int fa[N], vis[N];
deque<int>q;
int inblossom[N];
int lca(int u, int v) {
bool inpath[N] = { false };
while (1) {
u = fa[u];
inpath[u] = true;
if (match[u] == -1)break;
u = pre[match[u]];
}
while (1) {
v = fa[v];
if (inpath[v])return v;
v = pre[match[v]];
}
return v;
}
void reset(int u, int anc) {
while (u != anc) {
int v = match[u];
inblossom[fa[u]] = 1;
inblossom[fa[v]] = 1;
v = pre[v];
if (fa[v] != anc)pre[v] = match[u];
u = v;
}
}
void contract(int u, int v) {
int anc = lca(u, v);
memset(inblossom, 0, sizeof(inblossom));
reset(u, anc); reset(v, anc);
if (fa[u] != anc)pre[u] = v;
if (fa[v] != anc)pre[v] = u;
for (int i = 1; i <= n; i++)
if (inblossom[fa[i]]) {
fa[i] = anc;
if (!vis[i]) {
q.push_back(i);
vis[i] = 1;
}
}
}
bool dfs(int s) {
for (int i = 0; i <= n; ++i) {
pre[i] = -1; vis[i] = 0; fa[i] = i;
}
q.clear();
q.push_back(s); vis[s] = 1;
while (!q.empty()) {
int u = q.front(); q.pop_front();
for (int v = 1; v <= n; ++v) {
if (g[u][v] && fa[v] != fa[u] && match[u] != v) {
if (v == s || (match[v] != -1 && pre[match[v]] != -1))contract(u, v);
else if (pre[v] == -1) {
pre[v] = u;
if (match[v] != -1)q.push_back(match[v]), vis[match[v]] = 1;
else {
u = v;
while (u != -1) {
v = pre[u];
int w = match[v];
match[u] = v;
match[v] = u;
u = w;
}
return true;
}
}
}
}
}
return false;
}
int main(void)
{
while (~scanf("%d", &n)) {
memset(match, -1, sizeof match);
memset(g, 0, sizeof g);
int a, b;
while (scanf("%d%d", &a, &b) != EOF && a != 0)
g[a][b] = g[b][a] = 1;
int res = 0;
for (int i = 1; i <= n; ++i)
if (match[i] == -1 && dfs(i))
res++;
printf("%d\n", res * 2);
for (int i = 1; i <= n; ++i)
if (match[i] != -1) {
printf("%d %d\n", i, match[i]);
match[i] = match[match[i]] = -1;
}
}
return 0;
}
Boke and Tsukkomi - HDU - 4687
大致题意:
N个人,每个人有两种身份,然后进行匹配,问最大匹配情况下,哪些边是多余的
解题思路:
求多余的边,考虑割边
枚举每条边,跑一遍一般图带花树求最大匹配
AC代码:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <cmath>
#include <algorithm>
#include <queue>
#include<vector>
using namespace std;
const int N = 310;
int n, m;
int g[N][N], match[N];
int pre[N], inblossom[N];
int fa[N], vis[N];
deque<int>q;
vector<int>ans;
int x, y;
struct node {
int a, b;
}edge[N];
int lca(int u, int v) {
bool inpath[N] = { false };
while (1) {
u = fa[u];
inpath[u] = true;
if (match[u] == -1)break;
u = pre[match[u]];
}
while (1) {
v = fa[v];
if (inpath[v])return v;
v = pre[match[v]];
}
return v;
}
void reset(int u, int anc) {
while (u != anc) {
int v = match[u];
inblossom[fa[u]] = 1;
inblossom[fa[v]] = 1;
v = pre[v];
if (fa[v] != anc)pre[v] = match[u];
u = v;
}
}
void contract(int u, int v) {
int anc = lca(u, v);
memset(inblossom, 0, sizeof(inblossom));
reset(u, anc); reset(v, anc);
if (fa[u] != anc)pre[u] = v;
if (fa[v] != anc)pre[v] = u;
for (int i = 1; i <= n; i++)
if (inblossom[fa[i]]) {
fa[i] = anc;
if (!vis[i]) {
q.push_back(i);
vis[i] = 1;
}
}
}
bool dfs(int s) {
for (int i = 0; i <= n; ++i) {
pre[i] = -1; vis[i] = 0; fa[i] = i;
}
q.clear();
q.push_back(s); vis[s] = 1;
while (!q.empty()) {
int u = q.front(); q.pop_front();
for (int v = 1; v <= n; ++v) {
if (g[u][v] && fa[v] != fa[u] && match[u] != v) {
if (v == s || (match[v] != -1 && pre[match[v]] != -1))contract(u, v);
else if (pre[v] == -1) {
pre[v] = u;
if (match[v] != -1)q.push_back(match[v]), vis[match[v]] = 1;
else {
u = v;
while (u != -1) {
v = pre[u];
int w = match[v];
match[u] = v;
match[v] = u;
u = w;
}
return true;
}
}
}
}
}
return false;
}
int solve() {
memset(match, -1, sizeof match);
int res = 0;
for (int i = 1; i <= n; ++i)
if (match[i] == -1 && dfs(i))
res++;
return res;
}
int main(void)
{
while (~scanf("%d%d", &n, &m)) {
ans.clear();
memset(g, 0, sizeof g);
int a, b;
for (int i = 1; i <= m; ++i) {
scanf("%d%d", &a, &b);
edge[i] = { a,b };
g[a][b] = g[b][a] = 1;
}
int res = solve();
for (int i = 1; i <= m; ++i) {
int a = edge[i].a, b = edge[i].b;
memset(g, 0, sizeof g);
for (int j = 1; j <= m; ++j) {
if (i != j) {
int x = edge[j].a, y = edge[j].b;
if (x == a || x == b || y == a || y == b) continue;
g[x][y] = g[y][x] = 1;
}
}
int tmp = solve();
if (res != tmp + 1)ans.push_back(i);
}
int len = (int)ans.size();
printf("%d\n", len);
for (int i = 0; i < len; ++i)
printf("%d%c", ans[i], i == ans.size() - 1 ? '\n' : ' ');
if (!len)puts("");
}
return 0;
}