匹配问题
\(\text{hdu-1045}\)
有一个 \(n \times n\) 的图,. 代表空白区域,X 代表墙,现在要在空白区域放置结点,要求同一行同一列只能放一个,除非有墙阻隔,问最多能放多少个点。
\(1 \le n \le 4\)。
匹配问题,不过这题数据范围太小了,应该能用 dfs 做。但我们这里讨论匹配问题的解法。
这道题需要一个非常难想的 trick,因为如果 \((x,y)\) 放了一个点,在假设没有墙的情况下,对于 \(x\) 行的其他点有限制,对于 \(y\) 列的其他点也有限制,那么把行作为左部点、列作为右部点时,我们完全可以把 \(x\) 行编号 \(x\),\(y\) 列编号 \(y+eps\)(要求编号不同,直接加上个偏移量)。
此时再考虑有墙的情况,因为有墙的阻隔,隔离开的两个同行或同列的点没有限制。我们只需要给他们分配不同的编号即可,因为编号不同则认为没有限制。
考虑建图:源点向左部点连边,容量为 \(1\);右部点向汇点连边,容量为 \(1\);若一个点行编号为 \(x\),列编号为 \(y\),则 \(x \to y\) 连边,容量为 \(1\)。
这样如果匹配 \(x,y\),则表示在对应坐标放一个点,同时,同编号的至多匹配一次。
最大流即为最大匹配。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define ll long long
#define MAXN 1005
#define INF 0x3f3f3f3f
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
ll n, a[5][5], b[5][5], hd[MAXN], nxt[MAXN], tot = -1, s, t;
ll dep[MAXN], cur[MAXN], v[MAXN], w[MAXN];
char mp[5][5];
void add(ll x, ll y, ll z) {
nxt[++ tot] = hd[x], v[tot] = y, w[tot] = z, hd[x] = tot;
nxt[++ tot] = hd[y], v[tot] = x, w[tot] = 0, hd[y] = tot;
return;
}
bool bfs() {
memset(dep, 0, sizeof dep);
queue<ll> q; q.push(s), dep[s] = 1;
while(!q.empty()) {
ll x = q.front(); q.pop();
for(int i = hd[x]; i != -1; i = nxt[i])
if(!dep[v[i]] && w[i]) dep[v[i]] = dep[x] + 1, q.push(v[i]);
}
if(dep[t]) return 1;
return 0;
}
ll dfs(ll x, ll flow) {
if(x == t) return flow;
for(ll &i = cur[x]; i != -1; i = nxt[i])
if(dep[v[i]] == dep[x] + 1 && w[i]) {
ll d = dfs(v[i], min(flow, w[i]));
if(d > 0) {
w[i] -= d, w[i ^ 1] += d;
return d;
}
}
return 0;
}
ll dinic() {
ll res = 0;
while(bfs()) {
for(int i = 0; i <= 50; i ++) cur[i] = hd[i];
while(ll d = dfs(s, INF)) res += d;
}
return res;
}
int main() {
while(cin >> n) {
if(!n) break; s = 0, t = 50;
memset(hd, -1, sizeof hd), memset(nxt, -1, sizeof nxt);
memset(a, -1, sizeof a), memset(b, -1, sizeof b);
for(int i = 1; i <= n; i ++) cin >> (mp[i] + 1);
ll res = 1;
for(int i = 1; i <= n; i ++, res ++)
for(int j = 1; j <= n; j ++)
if(mp[i][j] == 'X') res ++;
else a[i][j] = res;
res = 1;
for(int j = 1; j <= n; j ++, res ++)
for(int i = 1; i <= n; i ++)
if(mp[i][j] == 'X') res ++;
else b[i][j] = res;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
add(a[i][j], b[i][j] + n * n, 1);
for(int i = 1; i <= n * n; i ++)
add(s, i, 1), add(i + n * n, t, 1);
cout << dinic() << "\n";
}
return 0;
}
\(\text{hdu-2444}\)
有一群学生。他们中的一些人可能彼此认识,而其他人则不认识。例如,A 和 B 互相认识,B 和 C 互相认识。但这并不意味着 A 和 C 互相认识。
现在,你得到了所有互相认识的学生对。你的任务是将学生分成两组,使得同一组中的任意两名学生互不认识。如果可以实现这个目标,那么就将他们安排到双人间。记住,只有之前给定的互相认识的学生对才能住在同一个房间里,这意味着只有互相认识的学生才能住在同一个房间里。
计算可以安排进这些双人间的最大对数。无解输出 No。
\(2 \le n \le 200\)。
先跑二分图染色,不是二分图就无解,输出 No。
然后让源点连左部点,左部点连右部点,右部点连汇点即可,容量都为 \(1\)。
最大流即为最大匹配。
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<queue>
using namespace std;
#define ll long long
#define MAXN 100005
#define INF 0x3f3f3f3f
ll n, m, hd[MAXN], nxt[MAXN], tot = -1, s, t, col[MAXN];
ll dep[MAXN], cur[MAXN], v[MAXN], w[MAXN];
vector<ll> g[MAXN];
void add(ll x, ll y, ll z) {
nxt[++ tot] = hd[x], v[tot] = y, w[tot] = z, hd[x] = tot;
nxt[++ tot] = hd[y], v[tot] = x, w[tot] = 0, hd[y] = tot;
return;
}
bool ck(ll x, ll c) {
col[x] = c;
for(auto y : g[x]) if(col[y] == c ||
(!col[y] && ck(y, -c))) return 1;
return 0;
}
bool bfs() {
memset(dep, 0, sizeof dep);
queue<ll> q; q.push(s), dep[s] = 1;
while(!q.empty()) {
ll x = q.front(); q.pop();
for(int i = hd[x]; i != -1; i = nxt[i])
if(!dep[v[i]] && w[i]) dep[v[i]] = dep[x] + 1, q.push(v[i]);
}
if(dep[t]) return 1;
return 0;
}
ll dfs(ll x, ll flow) {
if(x == t) return flow;
for(ll &i = cur[x]; i != -1; i = nxt[i])
if(dep[v[i]] == dep[x] + 1 && w[i]) {
ll d = dfs(v[i], min(flow, w[i]));
if(d > 0) {
w[i] -= d, w[i ^ 1] += d;
return d;
}
}
return 0;
}
ll dinic() {
ll res = 0;
while(bfs()) {
for(int i = 0; i <= 2 * n + 1; i ++) cur[i] = hd[i];
while(ll d = dfs(s, INF)) res += d;
}
return res;
}
int main() {
while(cin >> n >> m) {
tot = -1;
memset(hd, -1, sizeof hd), memset(nxt, -1, sizeof nxt);
for(int i = 1; i <= n; i ++) col[i] = 0, g[i].clear();
for(int i = 1; i <= m; i ++) {
ll x, y; cin >> x >> y;
g[x].push_back(y), g[y].push_back(x);
}
s = 0, t = 2 * n + 1; ll fp = 0;
for(int i = 1; i <= n; i ++)
if(!col[i] && ck(i, 1)) { fp = 1; break; }
if(fp) { cout << "No\n"; continue; }
for(int i = 1; i <= n; i ++)
if(col[i] == 1) add(s, i, 1);
else add(i + n, t, 1);
for(int i = 1; i <= n; i ++) for(auto j : g[i]) {
if(i > j) continue; ll x = i, y = j;
if(col[y] == 1) swap(x, y); add(x, y + n, 1);
}
cout << dinic() << "\n";
}
return 0;
}
\(\text{hdu-1083}\)
给定 \(n\) 个学生和 \(p\) 个课程,每个学生可以选一门课程或不选。问是否可以找到 \(p\) 个学生使得他们选择的课程两两不同。有解输出 YES,无解输出 NO。
\(1 \le n \le 300\),\(1 \le p \le 100\)。
这道题题面描述太难懂了,我稍微修改了一下。
课程作为左部点,学生作为右部点,还是源点连左部点,左部点连右部点,右部点连汇点。
最大流即最大匹配,若最大匹配数为 \(p\) 则有解,否则无解。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define ll long long
#define MAXN 1000005
#define INF 0x3f3f3f3f
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
ll T, p, n, hd[MAXN], nxt[MAXN], tot = -1, s, t;
ll dep[MAXN], cur[MAXN], v[MAXN], w[MAXN];
void add(ll x, ll y, ll z) {
nxt[++ tot] = hd[x], v[tot] = y, w[tot] = z, hd[x] = tot;
nxt[++ tot] = hd[y], v[tot] = x, w[tot] = 0, hd[y] = tot;
return;
}
bool bfs() {
memset(dep, 0, sizeof dep);
queue<ll> q; q.push(s), dep[s] = 1;
while(!q.empty()) {
ll x = q.front(); q.pop();
for(int i = hd[x]; i != -1; i = nxt[i])
if(!dep[v[i]] && w[i]) dep[v[i]] = dep[x] + 1, q.push(v[i]);
}
if(dep[t]) return 1;
return 0;
}
ll dfs(ll x, ll flow) {
if(x == t) return flow;
for(ll &i = cur[x]; i != -1; i = nxt[i])
if(dep[v[i]] == dep[x] + 1 && w[i]) {
ll d = dfs(v[i], min(flow, w[i]));
if(d > 0) {
w[i] -= d, w[i ^ 1] += d;
return d;
}
}
return 0;
}
ll dinic() {
ll res = 0;
while(bfs()) {
for(int i = s; i <= t; i ++) cur[i] = hd[i];
while(ll d = dfs(s, INF)) res += d;
}
return res;
}
int main() {
T = read();
while(T --) {
tot = -1;
memset(hd, -1, sizeof hd), memset(nxt, -1, sizeof nxt);
p = read(), n = read(), s = 0, t = p + n + 1;
for(int i = 1; i <= p; i ++) {
ll k = read(), x;
while(k --) x = read(), add(i, x + p, 1);
}
for(int i = 1; i <= p; i ++) add(s, i, 1);
for(int i = 1; i <= n; i ++) add(i + p, t, 1);
if(dinic() == p) cout << "YES\n";
else cout << "NO\n";
}
return 0;
}
\(\text{hdu-1281}\)
小希和 Gardon 在玩一个游戏:对一个 \(n \times m\) 的棋盘,在格子里放尽量多的一些国际象棋里面的“车”,并且使得他们不能互相攻击,这当然很简单,但是 Gardon 限制了只有某些格子才可以放,小希还是很轻松的解决了这个问题(见下图)注意不能放车的地方不影响车的互相攻击。
所以现在 Gardon 想让小希来解决一个更难的问题,在保证尽量多的“车”的前提下,棋盘里有些格子是可以避开的,也就是说,不在这些格子上放车,也可以保证尽量多的“车”被放下。但是某些格子若不放子,就无法保证放尽量多的“车”,这样的格子被称做重要点。Gardon 想让小希算出有多少个这样的重要点,你能解决这个问题么?
\(2 \le n,m \le 100\)。
根据题意,可以直接建图,行作为左部点,列作为右部点。如果删掉一条边使得最大匹配减少了,那么这条边对应的点就是重要点。跑若干遍 dinic 就能求出来。
但是这样并不是最优解,因为每次都要重建图,实际上有个算法是专门解决这类问题的,Dulmage–Mendelsohn 分解 可以做到 \(O(|V| + |E|)\) 的时间复杂度,比 dinic 快多了,但是我还没学会,以后有机会了再学,反正 dinic 能过这题。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define ll long long
#define MAXN 100005
#define MAXM 1005
#define INF 0x3f3f3f3f
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
ll n, m, k, hd[MAXN], nxt[MAXN], tot = -1, s, t, cs;
ll dep[MAXN], cur[MAXN], v[MAXN], w[MAXN];
bool vis[MAXM][MAXM];
void add(ll x, ll y, ll z) {
nxt[++ tot] = hd[x], v[tot] = y, w[tot] = z, hd[x] = tot;
nxt[++ tot] = hd[y], v[tot] = x, w[tot] = 0, hd[y] = tot;
return;
}
bool bfs() {
memset(dep, 0, sizeof dep);
queue<ll> q; q.push(s), dep[s] = 1;
while(!q.empty()) {
ll x = q.front(); q.pop();
for(int i = hd[x]; i != -1; i = nxt[i])
if(!dep[v[i]] && w[i]) dep[v[i]] = dep[x] + 1, q.push(v[i]);
}
if(dep[t]) return 1;
return 0;
}
ll dfs(ll x, ll flow) {
if(x == t) return flow;
for(ll &i = cur[x]; i != -1; i = nxt[i])
if(dep[v[i]] == dep[x] + 1 && w[i]) {
ll d = dfs(v[i], min(flow, w[i]));
if(d > 0) {
w[i] -= d, w[i ^ 1] += d;
return d;
}
}
return 0;
}
ll dinic(ll x, ll y) {
s = 0, t = n + m + 1, tot = -1;
memset(hd, -1, sizeof hd), memset(nxt, -1, sizeof nxt);
for(int i = 1; i <= n; i ++) for(int j = 1; j <= m; j ++)
if(vis[i][j] && (x != i || y != j)) add(i, j + n, 1);
for(int i = 1; i <= n; i ++) add(s, i, 1);
for(int i = 1; i <= m; i ++) add(i + n, t, 1);
ll res = 0;
while(bfs()) {
for(int i = s; i <= t; i ++) cur[i] = hd[i];
while(ll d = dfs(s, INF)) res += d;
}
return res;
}
int main() {
while(cin >> n >> m >> k) {
for(int i = 1; i <= n; i ++) for(int j = 1; j <= m; j ++) vis[i][j] = 0;
for(int i = 1; i <= k; i ++) { ll x = read(), y = read(); vis[x][y] = 1; }
ll r2 = dinic(-1, -1), r1 = 0;
for(int i = 1; i <= n; i ++) for(int j = 1; j <= m; j ++)
if(vis[i][j] && r2 != dinic(i, j)) r1 ++;
cout << "Board " << (++ cs) << " have " << r1;
cout << " important blanks for " << r2 << " chessmen.\n";
}
return 0;
}
\(\text{hdu-2819}\)
给定一个 \(n \times n\) 矩阵,每个元素都等于 \(0\) 或 \(1\)。你可以交换任意两行或任意两列。找到一种方法使得所有对角线上的元素都等于 \(1\)。无解输出 \(-1\)。
\(1 \le n \le 100\)。
首先跑个行列的最大匹配,找到 \(n\) 个点行列两两不同,若最大匹配不为 \(n\) 则无解。
找到之后直接贪心交换列或行,只交换一种就行。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define ll long long
#define MAXN 200005
#define INF 0x3f3f3f3f
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
ll n, hd[MAXN], nxt[MAXN], v[MAXN], w[MAXN], tot, s, t;
ll dep[MAXN], cur[MAXN], a[MAXN], b[MAXN], cnt;
struct node { ll x, y; } q[MAXN];
void add(ll x, ll y, ll z) {
nxt[++ tot] = hd[x], v[tot] = y, w[tot] = z, hd[x] = tot;
nxt[++ tot] = hd[y], v[tot] = x, w[tot] = 0, hd[y] = tot;
return;
}
bool bfs() {
memset(dep, 0, sizeof dep);
queue<ll> q; q.push(s), dep[s] = 1;
while(!q.empty()) {
ll x = q.front(); q.pop();
for(int i = hd[x]; i != -1; i = nxt[i])
if(!dep[v[i]] && w[i]) dep[v[i]] = dep[x] + 1, q.push(v[i]);
}
if(dep[t]) return 1;
return 0;
}
ll dfs(ll x, ll flow) {
if(x == t) return flow;
for(ll &i = cur[x]; i != -1; i = nxt[i])
if(dep[v[i]] == dep[x] + 1 && w[i]) {
ll d = dfs(v[i], min(flow, w[i]));
if(d > 0) {
w[i] -= d, w[i ^ 1] += d;
return d;
}
}
return 0;
}
ll dinic() {
ll res = 0;
while(bfs()) {
for(int i = s; i <= t; i ++) cur[i] = hd[i];
while(ll d = dfs(s, INF)) res += d;
}
return res;
}
int main() {
while(cin >> n) {
tot = -1, s = 0, t = 2 * n + 1;
memset(hd, -1, sizeof hd), memset(nxt, -1, sizeof nxt);
for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) {
ll x; cin >> x;
if(x) add(i, j + n, 1);
}
for(int i = 1; i <= n; i ++) add(s, i, 1), add(i + n, t, 1);
if(dinic() != n) { cout << "-1\n"; continue; }
for(int i = 1; i <= n; i ++)
for(int j = hd[i]; j != -1; j = nxt[j])
if(!w[j]) a[i] = v[j] - n, b[v[j] - n] = i;
cnt = 0;
for(int i = 1; i <= n; i ++) if(a[i] != i) {
ll x = i, y = b[i]; q[++ cnt] = {x, y};
b[a[i]] = y, b[i] = x; swap(a[x], a[y]);
}
cout << cnt << "\n";
for(int i = 1; i <= cnt; i ++)
cout << "R " << q[i].x << " " << q[i].y << "\n";
}
return 0;
}
\(\text{hdu-4185}\)
给定一张 \(n \times n\) 的地图,. 为空地,# 为石油,取一次石油只能取 \(1 \times 2\) 或者 \(2 \times 1\) 的矩阵。
空地不能取,每个石油节点只能取一次,求最多的取石油次数。
\(1 \le T \le 100\),\(1 \le n \le 600\)。
每个点分配不同的编号,比如给 \((x,y)\) 分配编号 \(x \times n + y\)。然后每个点还要拆一下点,拆成左部点和右部点,源点连左部点,右部点连汇点。相邻则左部点连右部点,容量都为 \(1\)。
最大匹配除以二即为答案,因为会重复匹配。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cstring>
using namespace std;
#define ll int
#define MAXN 1205
#define MAXM 1000005
#define INF 0x3f3f3f3f
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
ll T, n, hd[MAXM], nxt[MAXM], v[MAXM], w[MAXM], tot;
ll cur[MAXM], dep[MAXM], s, t;
ll dx[] = { -1, 0, 1, 0 };
ll dy[] = { 0, -1, 0, 1 };
char mp[MAXN][MAXN];
void add(ll x, ll y, ll z) {
nxt[++ tot] = hd[x], v[tot] = y, w[tot] = z, hd[x] = tot;
nxt[++ tot] = hd[y], v[tot] = x, w[tot] = 0, hd[y] = tot;
return;
}
bool bfs() {
memset(dep, 0, sizeof dep);
queue<ll> q; q.push(s), dep[s] = 1;
while(!q.empty()) {
ll x = q.front(); q.pop();
for(int i = hd[x]; i != -1; i = nxt[i])
if(!dep[v[i]] && w[i]) dep[v[i]] = dep[x] + 1, q.push(v[i]);
}
if(dep[t]) return 1;
return 0;
}
ll dfs(ll x, ll flow) {
if(x == t) return flow;
for(ll &i = cur[x]; i != -1; i = nxt[i])
if(dep[v[i]] == dep[x] + 1 && w[i]) {
ll d = dfs(v[i], min(flow, w[i]));
if(d > 0) {
w[i] -= d, w[i ^ 1] += d;
return d;
}
}
return 0;
}
ll dinic() {
ll res = 0;
while(bfs()) {
for(int i = s; i <= t; i ++) cur[i] = hd[i];
while(ll d = dfs(s, INF)) res += d;
}
return res;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> T;
for(int cs = 1; cs <= T; cs ++) {
cin >> n; s = 0, t = n * n * 2 + 1, tot = -1;
memset(hd, -1, sizeof hd), memset(nxt, -1, sizeof nxt);
for(int i = 0; i < n; i ++) cin >> mp[i];
for(int i = 0; i < n; i ++) for(int j = 0; j < n; j ++)
if(mp[i][j] == '#') for(int k = 0; k < 4; k ++) {
ll x = i + dx[k], y = j + dy[k];
if(x < 0 || y < 0 || x > n || y > n) continue;
if(mp[x][y] == '#') add(i * n + j, x * n + y + n * n, 1);
}
for(int i = 0; i < n; i ++) for(int j = 0; j < n; j ++)
add(s, i * n + j, 1), add(i * n + j + n * n, t, 1);
cout << "Case " << cs << ": " << dinic() / 2 << "\n";
}
return 0;
}
\(\text{hdu-1054}\)
给定一棵树,求最小点覆盖。
\(1 \le n \le 1500\)。
树上,最小点覆盖,直接树形 dp 不就完了。跟没有上司的舞会相似。
另外,如果是二分图的话,最小点覆盖等于最大匹配。
#include<iostream>
#include<cstdio>
#include<vector>
#include<cmath>
using namespace std;
#define ll long long
#define MAXN 2005
#define INF 0x3f3f3f3f
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
vector<ll> v[MAXN];
ll n, f[MAXN][2];
void dfs(ll x, ll fa) {
f[x][1] = 1;
for(auto y : v[x]) if(y != fa) {
dfs(y, x), f[x][0] += f[y][1];
f[x][1] += min(f[y][0], f[y][1]);
}
return;
}
int main() {
while(cin >> n) {
for(int i = 1; i <= n; i ++)
v[i].clear(), f[i][0] = f[i][1] = 0;
for(int i = 1; i <= n; i ++) {
ll x = read() + 1, k = read(), y;
while(k --) y = read() + 1,
v[x].push_back(y), v[y].push_back(x);
}
ll res = INF;
for(int i = 1; i <= n; i ++) if(v[i].size()) {
dfs(i, 0);
res = min(res, min(f[i][0], f[i][1]));
break;
}
cout << res << "\n";
}
return 0;
}
\(\text{hdu-1151}\)
给定一张有向无环图,求最小路径覆盖数。
\(1 \le n \le 120\)。
一个重要结论是最小路径覆盖数等于 \(n\) 减去最大匹配,证明是显然的,没有匹配时需要 \(n\) 个路径覆盖整张图,每多一个匹配,那么路径数就会减一。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define ll long long
#define MAXN 200005
#define INF 0x3f3f3f3f
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
ll T, n, m, s, t, tot = -1, hd[MAXN], nxt[MAXN], in[MAXN];
ll dep[MAXN], cur[MAXN], v[MAXN], w[MAXN], res;
vector<ll> g[MAXN];
void add(ll x, ll y, ll W) {
nxt[++ tot] = hd[x], v[tot] = y;
w[tot] = W, hd[x] = tot; return;
}
bool bfs() {
memset(dep, 0, sizeof dep);
queue<ll> q; q.push(s), dep[s] = 1;
while(!q.empty()) {
ll x = q.front(); q.pop();
for(int i = hd[x]; i != -1; i = nxt[i])
if(!dep[v[i]] && w[i]) dep[v[i]] = dep[x] + 1, q.push(v[i]);
}
if(dep[t] > 0) return 1;
return 0;
}
ll dfs(ll x, ll flow) {
if(x == t) return flow;
for(ll &i = cur[x]; i != -1; i = nxt[i])
if(dep[v[i]] == dep[x] + 1 && w[i] != 0) {
ll d = dfs(v[i], min(flow, w[i]));
if(d > 0) {
w[i] -= d, w[i ^ 1] += d;
return d;
}
}
return 0;
}
void dfs1(ll x) {
cout << x << " ";
for(auto y : g[x]) dfs1(y);
return;
}
int main() {
T = read();
while(T --) {
n = read(), m = read(), s = 0, t = 2 * n + 1;
memset(hd, -1, sizeof hd), memset(nxt, -1, sizeof nxt);
for(int i = 1; i <= m; i ++) {
ll x = read(), y = read();
add(x, y + n, 1), add(y + n, x, 0);
}
for(int i = 1; i <= n; i ++)
add(s, i, 1), add(i, s, 0),
add(i + n, t, 1), add(t, i + n, 0);
res = 0;
while(bfs()) {
for(int i = 0; i <= 2 * n + 1; i ++) cur[i] = hd[i];
while(ll d = dfs(s, INF)) res += d;
}
cout << n - res << "\n";
}
return 0;
}
\(\text{poj-2594}\)
你有没有读过有关寻宝的书籍?有没有看过有关寻宝的电影?有没有自己去探寻过宝藏?如果你从未有过这样的经历,你将永远不会知道寻宝带给你的乐趣。
最近,一家名为 EUC(探索未知公司)的公司计划探索火星上的一个未知地点,据说那里到处都是宝藏。由于技术的快速发展和对人类环境的不利影响,EUC派遣了一些机器人去探索这些宝藏。
为了简化问题,我们使用一个由 \(n\) 个点(这些 \(n\) 个点从 \(1\) 到 \(n\) 编号)组成的图来表示待探索的地点。一些点通过单向道路相连,这意味着通过这条道路,机器人只能从一端移动到另一端,而不能返回。由于某些未知原因,这个图中没有环。机器人可以通过火箭从地球被送到任何一个点。着陆后,机器人可以通过道路访问一些点,并且可以选择一些在其道路上的点进行探索。你应该注意到,两个不同机器人的道路可能包含一些相同的点。
出于财务原因,EUC 希望使用最少数量的机器人来探索火星上的所有点。
作为一个拥有出色编程技能的 ICPCer,你能帮助 EUC 吗?
\(1 \le n \le 500\),\(0 \le m \le 5000\)。
实际上就是有向无环图,求最小路径覆盖数,但一个点可以被覆盖多次。
看起来不太容易转化成网络流或者费用流直接求解。
考虑在原图上做一些操作,如果 \(x,y\) 和 \(y,z\) 可达,那么 \(x,z\) 也可达,我们直接添加一条 \(x \to z\) 的边,相当于经过了 \(y\) 但没有花费 \(x \to y\) 和 \(y \to z\) 的流量,相当于 \(y\) 可被多次覆盖。
新建图仍然满足最小路径覆盖数等于 \(n\) 减去最大匹配。可以用 Floyd 传递闭包处理新建图。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define ll long long
#define MAXN 1005
#define MAXM 2000005
#define INF 0x3f3f3f3f
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
ll T, n, m, s, t, tot = -1, hd[MAXN], nxt[MAXM], f[MAXN][MAXN];
ll dep[MAXN], cur[MAXN], v[MAXM], w[MAXM], res;
void add(ll x, ll y, ll W) {
nxt[++ tot] = hd[x], v[tot] = y;
w[tot] = W, hd[x] = tot; return;
}
bool bfs() {
memset(dep, 0, sizeof dep);
queue<ll> q; q.push(s), dep[s] = 1;
while(!q.empty()) {
ll x = q.front(); q.pop();
for(int i = hd[x]; i != -1; i = nxt[i])
if(!dep[v[i]] && w[i]) dep[v[i]] = dep[x] + 1, q.push(v[i]);
}
if(dep[t] > 0) return 1;
return 0;
}
ll dfs(ll x, ll flow) {
if(x == t) return flow;
for(ll &i = cur[x]; i != -1; i = nxt[i])
if(dep[v[i]] == dep[x] + 1 && w[i] != 0) {
ll d = dfs(v[i], min(flow, w[i]));
if(d > 0) {
w[i] -= d, w[i ^ 1] += d;
return d;
}
}
return 0;
}
int main() {
while(cin >> n >> m) {
if(!n && !m) break; s = 0, t = 2 * n + 1, tot = -1;
memset(hd, -1, sizeof hd), memset(nxt, -1, sizeof nxt);
for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) f[i][j] = 0;
for(int i = 1; i <= m; i ++) { ll x = read(), y = read(); f[x][y] = 1; }
for(int k = 1; k <= n; k ++) for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++) if(f[i][k] && f[k][j]) f[i][j] = 1;
for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++)
if(f[i][j]) add(i, j + n, 1), add(j + n, i, 0);
for(int i = 1; i <= n; i ++)
add(s, i, 1), add(i, s, 0),
add(i + n, t, 1), add(t, i + n, 0);
res = 0;
while(bfs()) {
for(int i = s; i <= t; i ++) cur[i] = hd[i];
while(ll d = dfs(s, INF)) res += d;
}
cout << n - res << "\n";
}
return 0;
}
\(\text{hdu-2768}\)
最新的真人秀节目已经上线:"猫与狗"。在这个节目中,一群猫和狗竞争非常有声望的最佳宠物称号。在每一集中,猫和狗都会展示自己,之后观众投票决定哪些宠物应该留下,哪些宠物应该被迫离开节目。
每位观众可以对两个方面投票:一只应该留在节目中的宠物和一只应该被淘汰的宠物。此外,基于一个普遍的事实,即每个人要么是猫爱好者(即狗恨者),要么是狗爱好者(即猫恨者),因此决定每个投票必须准确命名一只猫和一只狗。
聪明的制作人决定使用一种先进的程序,确保尽可能多的观众继续观看节目:留下的宠物将被选择,以最大化满足观众的意见数量。编写一个程序来计算这个最大满意观众的数量。
\(1 \le T \le 100\),\(1 \le c,d \le 100\),\(0 \le n \le 500\)。
如果两个人条件有冲突,那么两个人只能选一个,建图后最大独立集即为答案。
最大独立集等于 \(n\) 减去最小点覆盖,最小点覆盖等于最大匹配。
这显然是个二分图,可以二分图染色之后分成左部点和右部点建图。
不过有种比较省事的方法,可以把每个点拆成两个点,就不需要考虑左部点还是右部点。
直白的建图就好了,但是最大匹配等于最大流除以二。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define ll long long
#define MAXN 1005
#define MAXM 2000005
#define INF 0x3f3f3f3f
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
ll T, c, d, n, s, t, tot, hd[MAXN], nxt[MAXM], f[MAXN][MAXN];
ll dep[MAXN], cur[MAXN], v[MAXM], w[MAXM], res;
string a[MAXN], b[MAXN];
void add(ll x, ll y, ll z) {
nxt[++ tot] = hd[x], v[tot] = y, w[tot] = z, hd[x] = tot;
nxt[++ tot] = hd[y], v[tot] = x, w[tot] = 0, hd[y] = tot;
return;
}
bool bfs() {
memset(dep, 0, sizeof dep);
queue<ll> q; q.push(s), dep[s] = 1;
while(!q.empty()) {
ll x = q.front(); q.pop();
for(int i = hd[x]; i != -1; i = nxt[i])
if(!dep[v[i]] && w[i]) dep[v[i]] = dep[x] + 1, q.push(v[i]);
}
if(dep[t] > 0) return 1;
return 0;
}
ll dfs(ll x, ll flow) {
if(x == t) return flow;
for(ll &i = cur[x]; i != -1; i = nxt[i])
if(dep[v[i]] == dep[x] + 1 && w[i] != 0) {
ll d = dfs(v[i], min(flow, w[i]));
if(d > 0) {
w[i] -= d, w[i ^ 1] += d;
return d;
}
}
return 0;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> T;
while(T --) {
cin >> c >> d >> n; s = 0, t = 2 * n + 1, tot = -1;
memset(hd, -1, sizeof hd), memset(nxt, -1, sizeof nxt);
for(int i = 1; i <= n; i ++) cin >> a[i] >> b[i];
for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++)
if(a[i] == b[j] || b[i] == a[j]) add(i, j + n, 1);
for(int i = 1; i <= n; i ++) add(s, i, 1), add(i + n, t, 1);
res = 0;
while(bfs()) {
for(int i = s; i <= t; i ++) cur[i] = hd[i];
while(ll d = dfs(s, INF)) res += d;
}
cout << n - res / 2 << "\n";
}
return 0;
}
\(\text{hdu-2255}\)
有 \(n\) 个老百姓和 \(n\) 栋房子,每个老百姓要买恰好一栋房子,一栋房子只能被恰好一个人买。
第 \(i\) 个人买第 \(j\) 栋房子愿意出价 \(a_{i,j}\) 万元,村长最后来统一分配房子并收钱。
求满足条件的情况下,村长最多能收多少钱。
\(1 \le n \le 300\)。
显然是要求最大流最大费用,把费用取负,跑最大流最小费用就好了。
SSP 会 TLE,要用 KM 写。
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
#define ll long long
#define MAXN 305
#define INF 0x3f3f3f3f3f3f3f3f
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
ll n, a[MAXN][MAXN], lx[MAXN], ly[MAXN], lk[MAXN], s[MAXN];
ll vsx[MAXN], vsy[MAXN], px, py;
bool dfs(ll x) {
vsx[x] = 1;
for(int y = 1; y <= n; y ++) {
if(vsy[y]) continue;
ll res = lx[x] + ly[y] - a[x][y];
if(!res) {
vsy[y] = 1;
if(lk[y] == -1 || dfs(lk[y])) {
lk[y] = x;
return 1;
}
}
else if(s[y] > res) s[y] = res;
}
return 0;
}
ll KM() {
memset(lk, -1, sizeof lk), memset(ly, 0, sizeof ly);
for(int i = 1; i <= px; i ++) {
lx[i] = -INF;
for(int j = 1; j <= py; j ++)
if(a[i][j] > lx[i]) lx[i] = a[i][j];
}
for(int x = 1; x <= px; x ++) {
for(int i = 1; i <= py; i ++) s[i] = INF;
while(1) {
memset(vsx, 0, sizeof vsx);
memset(vsy, 0, sizeof vsy);
if(dfs(x)) break;
ll d = INF;
for(int i = 1; i <= py; i ++)
if(!vsy[i] && d > s[i]) d = s[i];
for(int i = 1; i <= px; i ++) if(vsx[i]) lx[i] -= d;
for(int i = 1; i <= py; i ++)
if(vsy[i]) ly[i] += d;
else s[i] -= d;
}
}
ll res = 0;
for(int i = 1; i <= py; i ++)
if(lk[i] != -1) res += a[lk[i]][i];
return res;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
while(cin >> n) {
px = py = n;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++) cin >> a[i][j];
cout << KM() << "\n";
}
return 0;
}
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/19812096

浙公网安备 33010602011771号