1.伊基的故事 I - 道路重建
AcWing 2236. 伊基的故事 I - 道路重建
伊基是一个小国 – 凤凰国的国王。
凤凰国是如此之小,以至于只有一个城市负责日常商品的生产,并使用公路网将商品运送到首都。
伊基发现本国最大的问题在于运输速度太慢了。
因为伊基以前是 ACM/ICPC 的参赛者,他意识到这其实是一个最大流问题。
他编写了一个最大流程序,并计算出了当前运输网络的最大运输能力。
他对运输速度的现状十分不满,并希望能够提高国家的运输能力。
提高运输能力的方法很简单,伊基将在运输网络中重建一些道路,以使这些道路具有更高的运输能力。
但是不幸的是,凤凰国的财力有限,道路建设经费只够重建一条道路。
伊基想要知道共有多少条道路可以纳入重建道路候选名单。
这些道路需要满足,将其重建后,国家的总运输能力能够增加。
最大流之关键边
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510, M = 10010, INF = 1e8;
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
bool vis_s[N], vis_t[N];
void add(int a, int b, int c){
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}
bool bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt){
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (d[ver] == -1 && f[i]){
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false;
}
int find(int u, int limit){
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i]){
cur[u] = i;
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i]){
int t = find(ver, min(f[i], limit - flow));
if (!t) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic(){
int r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
void dfs(int u, bool st[], int t){
st[u] = true;
for (int i = h[u]; ~i; i = ne[i]){
int j = i ^ t, ver = e[i];
if (f[j] && !st[ver])
dfs(ver, st, t);
}
}
int main(){
scanf("%d%d", &n, &m);
S = 0, T = n - 1;
memset(h, -1, sizeof h);
for (int i = 0; i < m; i ++ ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
dinic();
dfs(S, vis_s, 0);
dfs(T, vis_t, 1);
int res = 0;
for (int i = 0; i < m * 2; i += 2){
if (!f[i] && vis_s[e[i ^ 1]] && vis_t[e[i]]) res ++ ;
}
printf("%d\n", res);
return 0;
}
2.星际转移问题
AcWing 2187. 星际转移问题
由于人类对自然资源的消耗,人们意识到大约在 2300 年之后,地球就不能再居住了。于是在月球上建立了新的绿地,以便在需要时移民。
令人意想不到的是,2177 年冬由于未知的原因,地球环境发生了连锁崩溃,人类必须在最短的时间内迁往月球。
现有 n 个太空站(编号 1∼n)位于地球与月球之间,且有 m 艘公共交通太空船在其间来回穿梭。
每个太空站可容纳无限多的人,而每艘太空船 i 只可容纳 H[i] 个人。
每艘太空船将周期性地停靠一系列的太空站,例如:(1,3,4) 表示该太空船将周期性地停靠太空站 134134134…。
每一艘太空船从一个太空站驶往任一太空站耗时均为 1。
人们只能在太空船停靠太空站(或月球、地球)时上、下船。
初始时所有人全在地球上,太空船全在初始站,即行驶周期中的第一个站。
试设计一个算法,找出让所有人尽快地全部转移到月球上的运输方案。
最大流之最大流判定
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1101 * 50 + 10, M = (N + 1100 + 20 * 1101) + 10, INF = 1e8;
int n, m, k, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
struct Ship{
int h, r, id[30];
}ships[30];
int p[30];
int find(int x){
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int get(int i, int day){
return day * (n + 2) + i;
}
void add(int a, int b, int c){
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}
bool bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt){
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (d[ver] == -1 && f[i]){
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false;
}
int find(int u, int limit){
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i]){
cur[u] = i;
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i]){
int t = find(ver, min(f[i], limit - flow));
if (!t) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic(){
int r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
int main(){
scanf("%d%d%d", &n, &m, &k);
S = N - 2, T = N - 1;
memset(h, -1, sizeof h);
for (int i = 0; i < 30; i ++ ) p[i] = i;
for (int i = 0; i < m; i ++ ){
int a, b;
scanf("%d%d", &a, &b);
ships[i] = {a, b};
for (int j = 0; j < b; j ++ ){
int id;
scanf("%d", &id);
if (id == -1) id = n + 1;
ships[i].id[j] = id;
if (j){
int x = ships[i].id[j - 1];
p[find(x)] = find(id);
}
}
}
if (find(0) != find(n + 1)) puts("0");
else{
add(S, get(0, 0), k);
add(get(n + 1, 0), T, INF);
int day = 1, res = 0;
while (true){
add(get(n + 1, day), T, INF);
for (int i = 0; i <= n + 1; i ++ ) add(get(i, day - 1), get(i, day), INF);
for (int i = 0; i < m; i ++ ){
int r = ships[i].r;
int a = ships[i].id[(day - 1) % r], b = ships[i].id[day % r];
add(get(a, day - 1), get(b, day), ships[i].h);
}
res += dinic();
if (res >= k) break;
day ++ ;
}
printf("%d\n", day);
}
return 0;
}
3.最长递增子序列问题
AcWing 2180. 最长递增子序列问题
给定正整数序列 x1,⋯,xn。
- 计算其最长递增子序列的长度 s。
- 计算从给定的序列中最多可取出多少个长度为 s 的递增子序列。(给定序列中的每个元素最多只能被取出使用一次)
- 如果允许在取出的序列中多次使用 x1 和 xn,则从给定序列中最多可取出多少个长度为 s 的递增子序列。
注意:递增指非严格递增。
最大流之拆点
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010, M = 251010, INF = 1e8;
int n, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
int g[N], w[N];
void add(int a, int b, int c){
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}
bool bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt){
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (d[ver] == -1 && f[i]){
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false;
}
int find(int u, int limit){
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i]){
cur[u] = i;
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i]){
int t = find(ver, min(f[i], limit - flow));
if (!t) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic(){
int r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
int main(){
scanf("%d", &n);
S = 0, T = n * 2 + 1;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
int s = 0;
for (int i = 1; i <= n; i ++ ){
add(i, i + n, 1);
g[i] = 1;
for (int j = 1; j < i; j ++ ){
if (w[j] <= w[i]) g[i] = max(g[i], g[j] + 1);
}
for (int j = 1; j < i; j ++ ){
if (w[j] <= w[i] && g[j] + 1 == g[i]) add(n + j, i, 1);
}
s = max(s, g[i]);
if (g[i] == 1) add(S, i, 1);
}
for (int i = 1; i <= n; i ++ ){
if (g[i] == s) add(n + i, T, 1);
}
printf("%d\n", s);
if (s == 1) printf("%d\n%d\n", n, n);
else{
int res = dinic();
printf("%d\n", res);
for (int i = 0; i < idx; i += 2){
int a = e[i ^ 1], b = e[i];
if (a == S && b == 1) f[i] = INF;
else if (a == 1 && b == n + 1) f[i] = INF;
else if (a == n && b == n + n) f[i] = INF;
else if (a == n + n && b == T) f[i] = INF;
}
printf("%d\n", res + dinic());
}
return 0;
}
4.网络战争
AcWing 2279. 网络战争
给出一个带权无向图 G=(V,E),每条边 e 有一个权
w
e
w_e
we。
求将点 s 和点 t 分开的一个边割集 C,使得该割集的平均边权最小,即最小化:
∑
e
∈
C
w
e
∣
C
∣
\frac{\sum\limits_{e\in C}w_e}{|C|}
∣C∣e∈C∑we
注意: 边割集的定义与最小割中的割边的集合不同。在本题中,一个边割集是指:将这些边删去之后,s 与 t 不再连通。
最小割
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, M = 810, INF = 1e8;
const double eps = 1e-8;
int n, m, S, T;
int h[N], e[M], w[M], ne[M], idx;
double f[M];
int q[N], d[N], cur[N];
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, w[idx] = c, ne[idx] = h[b], h[b] = idx ++ ;
}
bool bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt){
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (d[ver] == -1 && f[i] > 0){
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false;
}
double find(int u, double limit){
if (u == T) return limit;
double flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i]){
cur[u] = i;
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i] > 0){
double t = find(ver, min(f[i], limit - flow));
if (t < eps) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
double dinic(double mid){
double res = 0;
for (int i = 0; i < idx; i += 2){
if (w[i] <= mid){
res += w[i] - mid;
f[i] = f[i ^ 1] = 0;
}
else f[i] = f[i ^ 1] = w[i] - mid;
}
double r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r + res;
}
int main(){
scanf("%d%d%d%d", &n, &m, &S, &T);
memset(h, -1, sizeof h);
while (m -- ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
double l = 0, r = 1e7;
while (r - l > eps){
double mid = (l + r) / 2;
if (dinic(mid) < 0) r = mid;
else l = mid;
}
printf("%.2lf\n", r);
return 0;
}
5.最优标号
AcWing 2280. 最优标号
给定一个无向图 G=(V,E),每个顶点都有一个标号,它是一个
[
0
,
2
31
−
1
]
[0,2^{31}−1]
[0,231−1] 内的整数。
不同的顶点可能会有相同的标号。
对每条边 (u,v),我们定义其费用 cost(u,v) 为 u 的标号与 v 的标号的异或值。
现在我们知道一些顶点的标号,你需要确定余下顶点的标号使得所有边的费用和尽可能小。
最小割
#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 510, M = (3000 + N * 2) * 2, INF = 1e8;
int n, m, k, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
int p[N];
PII edges[3010];
void add(int a, int b, int c1, int c2){
e[idx] = b, f[idx] = c1, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = c2, ne[idx] = h[b], h[b] = idx ++ ;
}
void build(int k){
memset(h, -1, sizeof h);
idx = 0;
for (int i = 0; i < m; i ++ ){
int a = edges[i].x, b = edges[i].y;
add(a, b, 1, 1);
}
for (int i = 1; i <= n; i ++ ){
if (p[i] >= 0){
if (p[i] >> k & 1) add(i, T, INF, 0);
else add(S, i, INF, 0);
}
}
}
bool bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt){
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (d[ver] == -1 && f[i]){
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false;
}
int find(int u, int limit){
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i]){
cur[u] = i;
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i]){
int t = find(ver, min(f[i], limit - flow));
if (!t) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
LL dinic(int k){
build(k);
int r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
int main(){
scanf("%d%d", &n, &m);
S = 0, T = n + 1;
for (int i = 0; i < m; i ++ ) scanf("%d%d", &edges[i].x, &edges[i].y);
scanf("%d", &k);
memset(p, -1, sizeof p);
while (k -- ){
int a, b;
scanf("%d%d", &a, &b);
p[a] = b;
}
LL res = 0;
for (int i = 0; i <= 30; i ++ ) res += dinic(i) << i;
printf("%lld\n", res);
return 0;
}
6.方格取数问题
P2774 方格取数问题
有一个
m
m
m 行
n
n
n 列的方格图,每个方格中都有一个正整数。现要从方格中取数,使任意两个数所在方格没有公共边,且取出的数的总和最大,请求出最大的和。
最小割
#include <cstdio>
#include <cctype>
#include <cstring>
#include <queue>
#define N 10010
#define E 100010
#define S 0
#define T (m * n + 1)
#define code(i, j) ((i - 1) * m + j)//点的线性标号
#define between(x, flo, top) (flo <= x and x <= top)//您是不是不喜欢这个qwq
int read() {
int res = 0, ch = getchar();
while (!isdigit(ch) and ch != EOF) ch = getchar();
while (isdigit(ch)) res = res * 10 + (ch - '0'), ch = getchar();
return res;
}
inline int min(int x, int y) { return (x < y) ? x : y; }
using std::queue;
const int d[4][2] = {{0, 1},{0, -1},{1, 0},{-1, 0}};
int m, n, sum = 0;
int first[N], nxt[E], to[E], val[E], cnt = 1;
void add(int u, int v, int w) {
++cnt;
to[cnt] = v;
val[cnt] = w;
nxt[cnt] = first[u];
first[u] = cnt;
}
int dep[N];
queue<int> q;
bool bfs() {
memset(dep, 0, sizeof(dep));
dep[S] = 1;
q.push(S);
while (not q.empty()) {
int u = q.front();
q.pop();
for (int p = first[u]; p; p = nxt[p]) {
int v = to[p];
if (dep[v]) continue;
if (val[p]) {//放心,开始都是正权的情况下,不会出现负数的
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
return dep[T];
}
int dfs(int u, int in) {
if (u == T) return in;
int out = 0;
for (int p = first[u]; p and in; p = nxt[p]) {
if (val[p] == 0) continue;
int v = to[p];
if (dep[v] != dep[u] + 1) continue;
int res = dfs(v, min(val[p], in));
val[p] -= res;
val[p ^ 1] += res;
in -= res;
out += res;
}
return out;
}
int main() {
n = read(), m = read();
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
int w = 0;
sum += w = getint();//假定全部都取,随后会删
if ((i + j) % 2 == 0) {//阵营A,源点连向自己,自己连向阵营B
add(S, code(i, j), w);
add(code(i, j), S, 0);
for (int k = 0; k <= 3; ++k) {
int x = i + d[k][0], y = j + d[k][1];
if (between(x, 1, n) and between(y, 1, m)) {
add(code(i, j), code(x, y), 2e9);
add(code(x, y), code(i, j), 0);
}
}
}
else {//阵营B,连向汇点
add(code(i, j), T, w);
add(T, code(i, j), 0);
}
}
}
int cut = 0;//最小割
while (bfs()) cut += dfs(S, 2e9);//最小割 = 最大流
printf("%d\n", sum - cut);
return 0;
}
7.最大获利
AcWing 961. 最大获利
新的技术正冲击着手机通讯市场,对于各大运营商来说,这既是机遇,更是挑战。
THU 集团旗下的 CS&T 通讯公司在新一代通讯技术血战的前夜,需要做太多的准备工作,仅就站址选择一项,就需要完成前期市场研究、站址勘测、最优化等项目。
在前期市场调查和站址勘测之后,公司得到了一共 N 个可以作为通讯信号中转站的地址,而由于这些地址的地理位置差异,在不同的地方建造通讯中转站需 要投入的成本也是不一样的,所幸在前期调查之后这些都是已知数据:建立第 i 个通讯中转站需要的成本为 Pi(1≤i≤N)。
另外公司调查得出了所有期望中的用户群,一共 M 个。关于第 i 个用户群的信息概括为 Ai,Bi 和 Ci:这些用户会使用中转站 Ai 和中转站 Bi 进行通讯,公司可以获益 Ci。(1≤i≤M,1≤Ai,Bi≤N)
THU 集团的 CS&T 公司可以有选择的建立一些中转站(投入成本),为一些用户提供服务并获得收益(获益之和)。
那么如何选择最终建立的中转站才能让公司的净获利最大呢?(净获利 = 获益之和 – 投入成本之和)
最小割之最大权闭合图
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 55010, M = (50000 * 3 + 5000) * 2 + 10, INF = 1e8;
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
void add(int a, int b, int c)
{
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}
bool bfs()
{
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int ver = e[i];
if (d[ver] == -1 && f[i])
{
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false;
}
int find(int u, int limit)
{
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i])
{
cur[u] = i;
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i])
{
int t = find(ver, min(f[i], limit - flow));
if (!t) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic()
{
int r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
int main()
{
scanf("%d%d", &n, &m);
S = 0, T = n + m + 1;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i ++ )
{
int p;
scanf("%d", &p);
add(m + i, T, p);
}
int tot = 0;
for (int i = 1; i <= m; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(S, i, c);
add(i, m + a, INF);
add(i, m + b, INF);
tot += c;
}
printf("%d\n", tot - dinic());
return 0;
}
8.生活的艰辛
AcWing 2324. 生活的艰辛
约翰是一家公司的 CEO。
公司的股东决定让他的儿子斯科特成为公司的经理。
约翰十分担心,儿子会因为在经理岗位上表现优异而威胁到他 CEO 的位置。
因此,他决定精心挑选儿子要管理的团队人员,让儿子知道社会的险恶。
已知公司中一共有 n 名员工,员工之间共有 m 对两两矛盾关系。
如果将一对有矛盾的员工安排在同一个团队,那么团队的管理难度就会增大。
一个团队的管理难度系数等于团队中的矛盾关系对数除以团队总人数。
团队的管理难度系数越大,团队就越难管理。
约翰希望给儿子安排的团队的管理难度系数尽可能大。
请帮帮他。
最小割之最大密度子图
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, M = (1000 + N * 2) * 2, INF = 1e8;
int n, m, S, T;
int h[N], e[M], ne[M], idx;
double f[M];
int q[N], d[N], cur[N];
int dg[N];
struct Edge{
int a, b;
}edges[M];
int ans;
bool st[N];
void add(int a, int b, double c1, double c2){
e[idx] = b, f[idx] = c1, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = c2, ne[idx] = h[b], h[b] = idx ++ ;
}
void build(double g){
memset(h, -1, sizeof h);
idx = 0;
for (int i = 0; i < m; i ++ ) add(edges[i].a, edges[i].b, 1, 1);
for (int i = 1; i <= n; i ++ ){
add(S, i, m, 0);
add(i, T, m + g * 2 - dg[i], 0);
}
}
bool bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt){
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (d[ver] == -1 && f[i] > 0){
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false;
}
double find(int u, double limit){
if (u == T) return limit;
double flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i]){
cur[u] = i;
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i] > 0){
double t = find(ver, min(f[i], limit - flow));
if (t <= 0) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
double dinic(double g){
build(g);
double r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
void dfs(int u){
st[u] = true;
if (u != S) ans ++ ;
for (int i = h[u]; ~i; i = ne[i]){
int ver = e[i];
if (!st[ver] && f[i] > 0) dfs(ver);
}
}
int main(){
scanf("%d%d", &n, &m);
S = 0, T = n + 1;
for (int i = 0; i < m; i ++ ){
int a, b;
scanf("%d%d", &a, &b);
dg[a] ++, dg[b] ++ ;
edges[i] = {a, b};
}
double l = 0, r = m;
while (r - l > 1e-8){
double mid = (l + r) / 2;
double t = dinic(mid);
if (m * n - t > 0) l = mid;
else r = mid;
}
dinic(l);
dfs(S);
if (!ans) puts("1\n1");
else{
printf("%d\n", ans);
for (int i = 1; i <= n; i ++ ){
if (st[i]) printf("%d\n", i);
}
}
return 0;
}
9.有向图破坏
AcWing 2325. 有向图破坏
爱丽丝和鲍勃正在玩以下游戏。
首先,爱丽丝绘制一个 N 个点 M 条边的有向图。然后,鲍勃试图毁掉它。
在每一步操作中,鲍勃都可以选取一个点,并将所有射入该点的边移除或者将所有从该点射出的边移除。
已知,对于第 i 个点,将所有射入该点的边移除所需的花费为
W
i
+
W_i^+
Wi+,将所有从该点射出的边移除所需的花费为
W
i
−
W_i^-
Wi−。
鲍勃需要将图中的所有边移除,并且还要使花费尽可能少。
请帮助鲍勃计算最少花费。
最小割之最小点权覆盖集
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 210, M = 5200 * 2 + 10, INF = 1e8;
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
bool st[N];
void add(int a, int b, int c){
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}
bool bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt){
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (d[ver] == -1 && f[i]){
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false;
}
int find(int u, int limit){
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i]){
cur[u] = i;
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i]){
int t = find(ver, min(f[i], limit - flow));
if (!t) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic(){
int r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
void dfs(int u){
st[u] = true;
for (int i = h[u]; ~i; i = ne[i]){
if (f[i] && !st[e[i]]) dfs(e[i]);
}
}
int main(){
scanf("%d%d", &n, &m);
S = 0, T = n * 2 + 1;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i ++ ){
int w;
scanf("%d", &w);
add(S, i, w);
}
for (int i = 1; i <= n; i ++ ){
int w;
scanf("%d", &w);
add(n + i, T, w);
}
while (m -- ){
int a, b;
scanf("%d%d", &a, &b);
add(b, n + a, INF);
}
printf("%d\n", dinic());
dfs(S);
int cnt = 0;
for (int i = 0; i < idx; i += 2){
int a = e[i ^ 1], b = e[i];
if (st[a] && !st[b]) cnt ++ ;
}
printf("%d\n", cnt);
for (int i = 0; i < idx; i += 2){
int a = e[i ^ 1], b = e[i];
if (st[a] && !st[b]){
if (a == S) printf("%d +\n", b);
}
}
for (int i = 0; i < idx; i += 2){
int a = e[i ^ 1], b = e[i];
if (st[a] && !st[b]){
if (b == T) printf("%d -\n", a - n);
}
}
return 0;
}
10.王者之剑
AcWing 2326. 王者之剑
给出一个 n×m 网格,每个格子上有一个价值
v
i
,
j
v_{i,j}
vi,j 的宝石。
Amber 可以自己决定起点,开始时刻为第 0 秒。
以下操作,在每秒内按顺序执行。
- 若第 i 秒开始时,Amber 在 (x,y),则 Amber 可以拿走 (x,y) 上的宝石。
- 在偶数秒时(i 为偶数),则 Amber 周围 4 格的宝石将会消失。
- 若第 i 秒开始时,Amber 在 (x,y),则在第 (i+1) 秒开始前,Amber 可以马上移动到相邻的格子 (x+1,y),(x−1,y),(x,y+1),(x,y−1) 或原地不动 (x,y)。
求 Amber 最多能得到多大总价值的宝石。
最小割之最大点权独立集
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10010, M = 60010, INF = 1e8;
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
int get(int x, int y){
return (x - 1) * m + y;
}
void add(int a, int b, int c){
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}
bool bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt){
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (d[ver] == -1 && f[i]){
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false;
}
int find(int u, int limit){
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i]){
cur[u] = i;
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i]){
int t = find(ver, min(f[i], limit - flow));
if (!t) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic(){
int r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
int main(){
scanf("%d%d", &n, &m);
S = 0, T = n * m + 1;
memset(h, -1, sizeof h);
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int tot = 0;
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= m; j ++ ){
int w;
scanf("%d", &w);
if (i + j & 1){
add(S, get(i, j), w);
for (int k = 0; k < 4; k ++ ){
int x = i + dx[k], y = j + dy[k];
if (x >= 1 && x <= n && y >= 1 && y <= m) add(get(i, j), get(x, y), INF);
}
}
else add(get(i, j), T, w);
tot += w;
}
}
printf("%d\n", tot - dinic());
return 0;
}
11.运输问题
AcWing 2192. 运输问题
W 公司有 m 个仓库和 n 个零售商店。
第 i 个仓库有
a
i
a_i
ai 个单位的货物;第 j 个零售商店需要
b
j
b_j
bj 个单位的货物。
货物供需平衡,即
∑
i
=
1
m
a
i
=
∑
j
=
1
n
b
j
\sum\limits_{i=1}^m a_i=\sum\limits_{j=1}^n b_j
i=1∑mai=j=1∑nbj。
从第 i 个仓库运送每单位货物到第 j 个零售商店的费用为
c
i
j
c_{ij}
cij。
试设计一个将仓库中所有货物运送到零售商店的运输方案。
对于给定的 m 个仓库和 n 个零售商店间运送货物的费用,计算最优运输方案和最差运输方案。
费用流
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 160, M = 5150 * 2 + 10, INF = 1e8;
int n, m, S, T;
int h[N], e[M], f[M], w[M], ne[M], idx;
int q[N], d[N], pre[N], incf[N];
bool st[N];
void add(int a, int b, int c, int d){
e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++ ;
}
bool spfa(){
int hh = 0, tt = 1;
memset(d, 0x3f, sizeof d);
memset(incf, 0, sizeof incf);
q[0] = S, d[S] = 0, incf[S] = INF;
while (hh != tt){
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (f[i] && d[ver] > d[t] + w[i]){
d[ver] = d[t] + w[i];
pre[ver] = i;
incf[ver] = min(incf[t], f[i]);
if (!st[ver]){
q[tt ++ ] = ver;
if (tt == N) tt = 0;
st[ver] = true;
}
}
}
}
return incf[T] > 0;
}
int EK(){
int cost = 0;
while (spfa()){
int t = incf[T];
cost += t * d[T];
for (int i = T; i != S; i = e[pre[i] ^ 1]){
f[pre[i]] -= t;
f[pre[i] ^ 1] += t;
}
}
return cost;
}
int main(){
scanf("%d%d", &m, &n);
S = 0, T = m + n + 1;
memset(h, -1, sizeof h);
for (int i = 1; i <= m; i ++ ){
int a;
scanf("%d", &a);
add(S, i, a, 0);
}
for (int i = 1; i <= n; i ++ ){
int b;
scanf("%d", &b);
add(m + i, T, b, 0);
}
for (int i = 1; i <= m; i ++ ){
for (int j = 1; j <= n; j ++ ){
int c;
scanf("%d", &c);
add(i, m + j, INF, c);
}
}
printf("%d\n", EK());
for (int i = 0; i < idx; i += 2){
f[i] += f[i ^ 1], f[i ^ 1] = 0;
w[i] = -w[i], w[i ^ 1] = -w[i ^ 1];
}
printf("%d\n", -EK());
return 0;
}
12.负载平衡问题
AcWing 2194. 负载平衡问题
G 公司有 n 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等。
如何用最少搬运量可以使 n 个仓库的库存数量相同。
搬运货物时,只能在相邻的仓库之间搬运。
数据保证一定有解。
费用流
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, M = 610, INF = 1e8;
int n, S, T;
int s[N];
int h[N], e[M], f[M], w[M], ne[M], idx;
int q[N], d[N], pre[N], incf[N];
bool st[N];
void add(int a, int b, int c, int d){
e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++ ;
}
bool spfa(){
int hh = 0, tt = 1;
memset(d, 0x3f, sizeof d);
memset(incf, 0, sizeof incf);
q[0] = S, d[S] = 0, incf[S] = INF;
while (hh != tt){
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (f[i] && d[ver] > d[t] + w[i]){
d[ver] = d[t] + w[i];
pre[ver] = i;
incf[ver] = min(f[i], incf[t]);
if (!st[ver]){
q[tt ++ ] = ver;
if (tt == N) tt = 0;
st[ver] = true;
}
}
}
}
return incf[T] > 0;
}
int EK(){
int cost = 0;
while (spfa()){
int t = incf[T];
cost += t * d[T];
for (int i = T; i != S; i = e[pre[i] ^ 1]){
f[pre[i]] -= t;
f[pre[i] ^ 1] += t;
}
}
return cost;
}
int main(){
scanf("%d", &n);
S = 0, T = n + 1;
memset(h, -1, sizeof h);
int tot = 0;
for (int i = 1; i <= n; i ++ ){
scanf("%d", &s[i]);
tot += s[i];
add(i, i < n ? i + 1 : 1, INF, 1);
add(i, i > 1 ? i - 1 : n, INF, 1);
}
tot /= n;
for (int i = 1; i <= n; i ++ ){
if (tot < s[i]) add(S, i, s[i] - tot, 0);
else if (tot > s[i]) add(i, T, tot - s[i], 0);
}
printf("%d\n", EK());
return 0;
}
13.分配问题
AcWing 2193. 分配问题
有 n 件工作要分配给 n 个人做。
第 i 个人做第 j 件工作产生的效益为
c
i
j
c_{ij}
cij。
试设计一个将 n 件工作分配给 n 个人做的分配方案。
对于给定的 n 件工作和 n 个人,计算最优分配方案和最差分配方案。
费用流之二分图最优匹配
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, M = 5210, INF = 1e8;
int n, S, T;
int h[N], e[M], f[M], w[M], ne[M], idx;
int q[N], d[N], pre[N], incf[N];
bool st[N];
void add(int a, int b, int c, int d){
e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++ ;
}
bool spfa(){
int hh = 0, tt = 1;
memset(d, 0x3f, sizeof d);
memset(incf, 0, sizeof incf);
q[0] = S, d[S] = 0, incf[S] = INF;
while (hh != tt){
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (f[i] && d[ver] > d[t] + w[i]){
d[ver] = d[t] + w[i];
pre[ver] = i;
incf[ver] = min(f[i], incf[t]);
if (!st[ver]){
q[tt ++ ] = ver;
if (tt == N) tt = 0;
st[ver] = true;
}
}
}
}
return incf[T] > 0;
}
int EK(){
int cost = 0;
while (spfa()){
int t = incf[T];
cost += t * d[T];
for (int i = T; i != S; i = e[pre[i] ^ 1]){
f[pre[i]] -= t;
f[pre[i] ^ 1] += t;
}
}
return cost;
}
int main(){
scanf("%d", &n);
S = 0, T = n * 2 + 1;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i ++ ){
add(S, i, 1, 0);
add(n + i, T, 1, 0);
}
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= n; j ++ ){
int c;
scanf("%d", &c);
add(i, n + j, 1, c);
}
}
printf("%d\n", EK());
for (int i = 0; i < idx; i += 2){
f[i] += f[i ^ 1], f[i ^ 1] = 0;
w[i] = -w[i], w[i ^ 1] = -w[i ^ 1];
}
printf("%d\n", -EK());
return 0;
}
14.数字梯形问题
AcWing 2191. 数字梯形问题
给定一个由 n 行数字组成的数字梯形如下图所示。
梯形的第一行有 m 个数字。
从梯形的顶部的 m 个数字开始,在每个数字处可以沿左下或右下方向移动,形成一条从梯形的顶至底的路径。
规则 1:从梯形的顶至底的 m 条路径互不相交。
规则 2:从梯形的顶至底的 m 条路径仅在数字结点处相交。
规则 3:从梯形的顶至底的 m 条路径允许在数字结点相交或边相交。
对于给定的数字梯形,分别按照规则 1,规则 2,和规则 3 计算出从梯形的顶至底的 m 条路径,使这 m 条路径经过的数字总和最大。
费用流之最大权不相交路径
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1200, M = 4000, INF = 1e8;
int m, n, S, T;
int h[N], e[M], f[M], w[M], ne[M], idx;
int q[N], d[N], pre[N], incf[N];
bool st[N];
int id[40][40], cost[40][40];
void add(int a, int b, int c, int d){
e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++ ;
}
bool spfa(){
int hh = 0, tt = 1;
memset(d, -0x3f, sizeof d);
memset(incf, 0, sizeof incf);
q[0] = S, d[S] = 0, incf[S] = INF;
while (hh != tt){
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (f[i] && d[ver] < d[t] + w[i]){
d[ver] = d[t] + w[i];
pre[ver] = i;
incf[ver] = min(f[i], incf[t]);
if (!st[ver]){
q[tt ++ ] = ver;
if (tt == N) tt = 0;
st[ver] = true;
}
}
}
}
return incf[T] > 0;
}
int EK(){
int cost = 0;
while (spfa()){
int t = incf[T];
cost += t * d[T];
for (int i = T; i != S; i = e[pre[i] ^ 1]){
f[pre[i]] -= t;
f[pre[i] ^ 1] += t;
}
}
return cost;
}
int main(){
int cnt = 0;
scanf("%d%d", &m, &n);
S = ++ cnt;
T = ++ cnt;
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= m + i - 1; j ++ ){
scanf("%d", &cost[i][j]);
id[i][j] = ++ cnt;
}
}
// 规则1
memset(h, -1, sizeof h), idx = 0;
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= m + i - 1; j ++ ){
add(id[i][j] * 2, id[i][j] * 2 + 1, 1, cost[i][j]);
if (i == 1) add(S, id[i][j] * 2, 1, 0);
if (i == n) add(id[i][j] * 2 + 1, T, 1, 0);
if (i < n){
add(id[i][j] * 2 + 1, id[i + 1][j] * 2, 1, 0);
add(id[i][j] * 2 + 1, id[i + 1][j + 1] * 2, 1, 0);
}
}
}
printf("%d\n", EK());
// 规则2
memset(h, -1, sizeof h), idx = 0;
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= m + i - 1; j ++ ){
add(id[i][j] * 2, id[i][j] * 2 + 1, INF, cost[i][j]);
if (i == 1) add(S, id[i][j] * 2, 1, 0);
if (i == n) add(id[i][j] * 2 + 1, T, INF, 0);
if (i < n){
add(id[i][j] * 2 + 1, id[i + 1][j] * 2, 1, 0);
add(id[i][j] * 2 + 1, id[i + 1][j + 1] * 2, 1, 0);
}
}
}
printf("%d\n", EK());
// 规则3
memset(h, -1, sizeof h), idx = 0;
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= m + i - 1; j ++ ){
add(id[i][j] * 2, id[i][j] * 2 + 1, INF, cost[i][j]);
if (i == 1) add(S, id[i][j] * 2, 1, 0);
if (i == n) add(id[i][j] * 2 + 1, T, INF, 0);
if (i < n){
add(id[i][j] * 2 + 1, id[i + 1][j] * 2, INF, 0);
add(id[i][j] * 2 + 1, id[i + 1][j + 1] * 2, INF, 0);
}
}
}
printf("%d\n", EK());
return 0;
}
15.K取方格数
AcWing 382. K取方格数
在一个 N×N 的矩形网格中,每个格子里都写着一个非负整数。
可以从左上角到右下角安排 K 条路线,每一步只能往下或往右,沿途经过的格子中的整数会被取走。
若多条路线重复经过一个格子,只取一次。
求能取得的整数的和最大是多少。
费用流之网格图模型
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5010, M = 20010, INF = 1e8;
int n, k, S, T;
int h[N], e[M], f[M], w[M], ne[M], idx;
int q[N], d[N], pre[N], incf[N];
bool st[N];
int get(int x, int y, int t){
return (x * n + y) * 2 + t;
}
void add(int a, int b, int c, int d){
e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++ ;
}
bool spfa(){
int hh = 0, tt = 1;
memset(d, -0x3f, sizeof d);
memset(incf, 0, sizeof incf);
q[0] = S, d[S] = 0, incf[S] = INF;
while (hh != tt){
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i]){
int ver = e[i];
if (f[i] && d[ver] < d[t] + w[i]){
d[ver] = d[t] + w[i];
pre[ver] = i;
incf[ver] = min(incf[t], f[i]);
if (!st[ver]){
q[tt ++ ] = ver;
if (tt == N) tt = 0;
st[ver] = true;
}
}
}
}
return incf[T] > 0;
}
int EK(){
int cost = 0;
while (spfa()){
int t = incf[T];
cost += t * d[T];
for (int i = T; i != S; i = e[pre[i] ^ 1]){
f[pre[i]] -= t;
f[pre[i] ^ 1] += t;
}
}
return cost;
}
int main(){
scanf("%d%d", &n, &k);
S = 2 * n * n, T = S + 1;
memset(h, -1, sizeof h);
add(S, get(0, 0, 0), k, 0);
add(get(n - 1, n - 1, 1), T, k, 0);
for (int i = 0; i < n; i ++ ){
for (int j = 0; j < n; j ++ ){
int c;
scanf("%d", &c);
add(get(i, j, 0), get(i, j, 1), 1, c);
add(get(i, j, 0), get(i, j, 1), INF, 0);
if (i + 1 < n) add(get(i, j, 1), get(i + 1, j, 0), INF, 0);
if (j + 1 < n) add(get(i, j, 1), get(i, j + 1, 0), INF, 0);
}
}
printf("%d\n", EK());
return 0;
}
16.捉迷藏
AcWing 379. 捉迷藏
Vani 和 cl2 在一片树林里捉迷藏。
这片树林里有 N 座房子,M 条有向道路,组成了一张有向无环图。
树林里的树非常茂密,足以遮挡视线,但是沿着道路望去,却是视野开阔。
如果从房子 A 沿着路走下去能够到达 B,那么在 A 和 B 里的人是能够相互望见的。
现在 cl2 要在这 N 座房子里选择 K 座作为藏身点,同时 Vani 也专挑 cl2 作为藏身点的房子进去寻找,为了避免被 Vani 看见,cl2 要求这 K 个藏身点的任意两个之间都没有路径相连。
为了让 Vani 更难找到自己,cl2 想知道最多能选出多少个藏身点。
最小路径重复点覆盖
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 210, M = 30010;
int n, m;
bool d[N][N], st[N];
int match[N];
bool find(int x){
for (int i = 1; i <= n; i ++ ){
if (d[x][i] && !st[i]){
st[i] = true;
int t = match[i];
if (t == 0 || find(t)){
match[i] = x;
return true;
}
}
}
return false;
}
int main(){
scanf("%d%d", &n, &m);
while (m -- ){
int a, b;
scanf("%d%d", &a, &b);
d[a][b] = true;
}
// 传递闭包
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] |= d[i][k] & d[k][j];
int res = 0;
for (int i = 1; i <= n; i ++ ){
memset(st, 0, sizeof st);
if (find(i)) res ++ ;
}
printf("%d\n", n - res);
return 0;
}
17.二分图结论
- 最大匹配的含义就是:最多选多少条边,使所有的边没有公共点(数值是边的数量)
- 最小点覆盖含义:选出最少的点,可以覆盖所有的边==最大匹配(数值是点的数量)
- 二分图最小边覆盖含义:边覆盖是图中一些边的集合,且对于图中所有的点,至少有一条集合中的边与其相关联,边数最小的覆盖就是最小边覆盖。
定理:最小边覆盖=图中点的个数-最大匹配。 - 最大独立集含义:选出最多的点,使得每两个点没有边相连(找出最少的点,把所有边破坏掉,那自然是最小点覆盖了,所以就是n-最小点覆盖)(数值是点的数量)
注意,这里的n是总点数 - 最小路径覆盖:在DAG上,用最少的互不相交的路径,将所有点覆盖(互不相交指的是任意两个路径无重复点和重复边)(数值是路径数目)
- 最小路径重复点覆盖:先求传递闭包,再做最小路径覆盖
最小路径覆盖的值=n-最大匹配数,这里的n是总点数
最大匹配数 = 最小点覆盖 = 总点数-最大独立集 = 总点数-最小路径覆盖