kuangbin专题十匹配问题

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值