\(\text{一:Dinic最大流}\)

最坏复杂度 \({\mathcal O(n^2m)}\) 一般可以处理 \(10^4\) ~ \(10^5\) 的网络。

struct Edge {
int v, w, nxt;
} E[M * 2]; int head[N], tot = 1;//记得从1存边 inline void add(int u, int v, int w) {
E[++tot] = (Edge) { v, w, head[u] };
head[u] = tot;
} int n, m, d[N], S, T; //bfs找增广路
bool bfs() {
queue<int> q;
memset(d, 0, sizeof(int) * (n + 1));
d[S] = 1;
q.push(S);
while (q.size()) {
int u = q.front();
q.pop();
for (register int i = head[u]; i; i = E[i].nxt) {
if (E[i].w and d[E[i].v] == 0) {
d[E[i].v] = d[u] + 1;
if (E[i].v == T) {
return true;
}
q.push(E[i].v);
}
}
}
return false;
} int Dinic(int u, int flow) {
if (u == T) {
return flow;
}
int k, rest = flow;
for (register int i = head[u]; i; i = E[i].nxt) {
int v = E[i].v;
if (E[i].w and d[v] == d[u] + 1) {
k = Dinic(v, min(E[i].w, rest));
if (k == 0) {
d[v] = 0;
}
E[i].w -= k;
E[i ^ 1].w += k;
rest -= k;
}
}
return flow - rest;
} //主函数中 for (register int u, v, w, i = 1; i <= m; ++ i) {
cin >> u >> v >> w;
add(u, v, w);//建边
add(v, u, 0);//反悔的边,记得w赋为0
} int maxflow = 0, flow = 0;
while (bfs())
while (flow = Dinic(S, INF))
maxflow += flow;

二:\(\text{Dinic费用流}\)

复杂度不知道,\(10^3\) ~ \(10^4\) 应该能跑。

struct Edge {
int v, w, cost, nxt;
} E[M * 2]; int head[N], tot = 1;//同上 inline void add(int u, int v, int w, int cost) {
E[++tot] = (Edge) { v, w, cost, head[u] }; //多加一维费用
head[u] = tot;
} bool vis[N];
int n, m, d[N], S, T, mincost; inline bool spfa() {//SPFA找增广路,要求增广路费用最小
queue<int> q;
memset(d, 0x3f, sizeof(int)*(n+1));
memset(vis, 0, (n+1));
d[S] = 0;
vis[S] = 1;
q.push(S);
while (q.size()) {
int u = q.front(), v;
q.pop();
vis[u] = 0;
for (register int i = head[u]; i; i = E[i].nxt) {
v = E[i].v;
if (E[i].w and d[v] > d[u] + E[i].cost) {
d[v] = d[u] + E[i].cost;
if (vis[v]) continue;
q.push(v);
vis[v] = 1;
}
}
}
return d[T] != d[0];
} int Dinic(int u, int flow) {
if (u == T) {
return flow;
}
vis[u] = 1;//别忘了vis
int k, rest = flow;
for (register int i = head[u]; i and rest; i = E[i].nxt) {
int v = E[i].v;
if ((!vis[v] or v == T) and E[i].w and d[u] + E[i].cost == d[v]) {//这里多了个限制条件。
k = Dinic(v, min(E[i].w, rest));
if (k == 0) {
d[v] = 0;
}
rest -= k;
E[i].w -= k;
E[i ^ 1].w += k;
mincost += E[i].cost * k;//累计贡献(费用)
}
}
return flow - rest;
} //主函数 for (register int i = 1; i <= m; ++ i) {
int u, v, c, w;
cin >> u >> v >> w >> c;
add(u, v, w, c);
add(v, u, 0, -c);//反悔的边cost为负
} int maxflow = 0, flow;
while (spfa()) {//与最大流略有不同。
vis[T] = 1;
while (vis[T]) {
memset(vis, 0, (n+1));
maxflow += Dinic(S, INF);
}
}

三:一些题目:

DAG最小路径覆盖

建拆点二分图,跑dinic(或匈牙利),答案 = 点数 - 最大流(最大匹配数)

重点在如何递归输出路径,看代码。

/*
拆点的时候要拆成i与i+n 最好不要拆成i<<1 and i<<1|1 否则会出现玄学错误
*/
#include<bits/stdc++.h>
#pragma GCC optimize(2) using namespace std; const int N = 5000, M = 6007; int n, m, s = 0, t = 3500;
int ver[M<<1], d[N<<1], edge[M<<1], nxt[M<<1], head[N<<1];
int pre[N<<1], succ[N<<1], tot = 1; void addEdge(int u, int v, int w) {
ver[++tot] = v; edge[tot] = w; nxt[tot] = head[u]; head[u] = tot;
} int bfs() {
queue<int> q;
memset(d, 0, sizeof d);
q.push(s); d[s] = 1;
while (q.size()) {
int v, u = q.front(); q.pop();
for (int i = head[u]; i; i = nxt[i]) {
if (edge[i] and !d[v = ver[i]]) {
d[v] = d[u] + 1;
if (v == t) return 1;
q.push(v);
}
}
}
return 0;
}
int dinic(int u, int flow) {
if (u == t) return flow;
int k, rest = flow, v;
for (int i = head[u]; i and rest; i = nxt[i]) {
if (edge[i] and d[v = ver[i]] == d[u] + 1) {
k = dinic(v, min(edge[i], rest));
if (!k) d[v] = 0;
rest -= k;
edge[i] -= k;
edge[i ^ 1] += k;
if (v != t and k and u != s) {//记录路径
succ[u] = v - n;
pre[v - n] = u;
}
}
}
return flow - rest;
} int vis[N<<1]; void put(int x) {//递归输出路径
if (!x) return;
if (pre[x] != x) put(pre[x]);
vis[x] = 1;
printf("%d ", x);
} int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++ i) {
int u, v;
scanf("%d", &u);
scanf("%d", &v);
addEdge(u, v + n, 1);
addEdge(v + n, u, 0);
}
for (int i = 1; i <= n; ++ i) {
addEdge(s, i, 1); addEdge(i, s, 0);
addEdge(i + n, t, 1); addEdge(t, i + n, 0);
}
for (int i = 1; i <= n; ++ i)
pre[i] = succ[i] = i;
int maxflow = 0, flow;
while (bfs())
while (flow = dinic(s, 0x3f3f3f3f))
maxflow += flow;
int ans = n - maxflow;
for (int i = n; i >= 1; -- i) {
if (succ[i] == i and !vis[i]) {//输出路径
put(i);
puts("");
}
}
printf("%d\n", ans);
}

试题库问题

对于每一个题型 \(i\) ,都可以选择一个题目 \(j\) ,那么就从 \(i\) 到 \(j\) 连一条边

那么就是二分图多重匹配

可以拆点匈牙利做, 但效率低下

考虑网络流,

对于一个左部节点 \(L\) 它要匹配 \(x\) 个右部节点,就从源点连一条容量为 \(x\) 的边。

#include<bits/stdc++.h>
#pragma GCC optimize(2) using namespace std; const int N = 50000, oo = 0x3f3f3f3f; int tot = 1, k, n, s, t;
int ver[N], edge[N], nxt[N], head[N];
vector<int> succ[N]; void add(int u, int v, int w) {
ver[++tot] = v;
edge[tot] = w;
nxt[tot] = head[u];
head[u] = tot;
ver[++tot] = u;
edge[tot] = 0;
nxt[tot] = head[v];
head[v] = tot;
} int d[N]; bool bfs() {
queue<int> q;
memset(d, 0, sizeof d);
d[s] = 1; q.push(s);
while (q.size()) {
int u = q.front(), y; q.pop();
for (int i = head[u]; i; i = nxt[i]) {
if (edge[i] and !d[y = ver[i]]) {
d[y] = d[u] + 1;
if (y == t) return 1;
q.push(y);
}
}
}
return 0;
} int dinic(int u, int flow) {
if (u == t) return flow;
int k, rest = flow;
for (int i = head[u]; i and rest; i = nxt[i]) {
int v = ver[i];
if (edge[i] and d[v] == d[u] + 1) {
k = dinic(v, min(edge[i], rest));
if (!k) d[v] = 0;
edge[i] -= k;
edge[i ^ 1] += k;
rest -= k;
}
}
return flow - rest;
} int main() {
int m = 0;
scanf("%d%d", &k, &n);
s = 0, t = 5000;
for (int i = 1; i <= k; ++ i) {
int x; scanf("%d", &x); m += x;
add(s, i, x);
}
for (int i = 1; i <= n; ++ i) {
int num; scanf("%d", &num);
for (int j = 1; j <= num; ++ j) {
int x; scanf("%d", &x);
add(x, i + k, 1);
}
add(i + k, t, 1);
}
int flow, maxflow = 0;
while (bfs())
while (flow = dinic(s, +oo))
maxflow += flow;
if (m != maxflow)
{cout << "No Solution!" << endl; return 0;}
for (int i = 1; i <= k; ++ i) {
printf("%d: ", i);
for (int j = head[i]; j; j = nxt[j]) {
if (!edge[j] and ver[j] != s and ver[j] != t)//根据残量网络输出。
printf("%d ", ver[j] - k);
}
puts("");
}
}

最长不下降子序列问题

第一问直接做 \(LIS\)

第二问建模:若 \(f[i]\) 能转移到 \(f[j]\) , 才向 \(i\) 到 \(j\) 连边.

第三问直接在残量网络上将 \(s\) 到 \(1\) 与 \(n\) 到 \(t\) 扩容(要讨论),继续找增广路。

#include<bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std; const int N = 50000, oo = 0x7f7f7f7f; int n, s, t;
int tot = 1, edge[N], ver[N], nxt[N], d[N], f[N], head[N];
int a[N]; void add(int u, int v, int w) {
ver[++tot] = v;
edge[tot] = w;
nxt[tot] = head[u];
head[u] = tot;
ver[++tot] = u;
edge[tot] = 0;
nxt[tot] = head[v];
head[v] = tot;
} int bfs() {
memset(d, 0, sizeof d);
queue<int> q;
q.push(s); d[s] = 1;
while (q.size()) {
int u = q.front(); q.pop();
for (int i = head[u]; i; i = nxt[i]) {
if (edge[i] and !d[ver[i]]) {
d[ver[i]] = d[u] + 1;
if (ver[i] == t) return 1;
q.push(ver[i]);
}
}
}
return 0;
} int dinic(int u, int flow) {
if (u == t) return flow;
int k, rest = flow, v;
for (int i = head[u]; i and rest; i = nxt[i]) {
if (edge[i] and d[v = ver[i]] == d[u] + 1) {
k = dinic(v, min(edge[i], rest));
if (!k) d[v] = 0;
edge[i] -= k;
edge[i ^ 1] += k;
rest -= k;
}
}
return flow - rest;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++ i)
scanf("%d", a + i); for (int i = 1; i <= n; ++ i) {
f[i] = 1;
for (int j = 1; j < i; ++ j)
if (a[i] >= a[j])
f[i] = max(f[j] + 1, f[i]);
}
int ans = 0;
for (int i = 1; i <= n; ++ i)
ans = max(ans, f[i]);
cout << ans << endl; int maxflow = 0, flow;
for (int i = 1; i <= n; ++ i)
for (int j = i + 1; j <= n; ++ j)
if (a[i] <= a[j] and f[j] == f[i] + 1)
add(i, j, 1);
s = 0, t = 1000;
for (int i = 1; i <= n; ++ i) {
if (f[i] == 1)
add(s, i, 1);
if (f[i] == ans) //想一想这里为什么不是"else if(f[i]==ans)"
add(i, t, 1); //调了我好久
}
while (bfs())
while (flow = dinic(s, +oo))
maxflow += flow;
cout << maxflow << endl; add(s, 1, +oo);
if (f[n] == ans)//注意,只有满足f[n]==ans的才能连一条+oo的边到t
add(n, t, +oo);
while (bfs())
while (flow = dinic(s, +oo)) //新加边后残量网络上跑的最大流
maxflow += flow; //加上第二问的答案就是第三问答案
cout << maxflow << endl;
}

方格取数问题

二分图带权最大独立集

\(Ans = sum - maxflow\)

先对每个格子黑白相间地染色,分成左部节点与右部节点。

对于每个左部节点和右部节点都有一个权值,要求选出的点权值最大且没有连边

建边方法:

\(1. s \xRightarrow{val[L]} L\)

\(2. L \xRightarrow{inf} R\)

\(3. R \xRightarrow{val[R]}t\)

跑出来的最大流就是答案。

#include<bits/stdc++.h>
#pragma GCC optimize(2) using namespace std; const int N = 50000, oo = 0x3f3f3f3f; int n, m, s, t;
int tot = 1, edge[N], ver[N], nxt[N], head[N];
int val[200][200], id[200][200]; void add(int u, int v, int w) {
ver[++tot] = v;
edge[tot] = w;
nxt[tot] = head[u];
head[u] = tot;
ver[++tot] = u;
edge[tot] = 0;
nxt[tot] = head[v];
head[v] = tot;
} const int tx[] = {1,0,-1,0};
const int ty[] = {0,1,0,-1}; void ADD(int x, int y) {
for (int i = 0; i < 4; ++ i) {
int nx = x + tx[i], ny = y + ty[i];
if (nx > 0 and nx <= n and ny > 0 and ny <= m)
add(id[x][y], id[nx][ny], +oo);
}
}
int d[N]; int bfs() {
memset(d, 0, sizeof d);
queue<int> q; q.push(s);
d[s] = 1;
while (q.size()) {
int u = q.front(); q.pop();
for (int i = head[u]; i; i = nxt[i]) {
if (edge[i] and !d[ver[i]]) {
d[ver[i]] = d[u] + 1;
if (ver[i] == t) return 1;
q.push(ver[i]);
}
}
}
return 0;
} int dinic(int u, int flow) {
if (u == t) return flow;
int k, rest = flow;
for (int i = head[u]; i and rest; i = nxt[i]) {
if (edge[i] and d[ver[i]] == d[u] + 1) {
k = dinic(ver[i], min(edge[i], rest));
if (!k) d[ver[i]] = 0;
rest -= k;
edge[i] -= k;
edge[i ^ 1] += k;
}
}
return flow - rest;
}
int main() {
int cnt = 0, sum = 0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j) {
int x; scanf("%d", &x);
sum += x;
id[i][j] = ++cnt;
val[i][j] = x;
}
s = 0, t = cnt + 11;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
if (i + j & 1) {
ADD(i,j);
add(s, id[i][j], val[i][j]);
} else
add(id[i][j], t, val[i][j]);
}
}
int maxflow = 0, flow;
while (bfs())
while (flow = dinic(s, +oo))
maxflow += flow;
printf("%d\n", sum - maxflow);
return 0;
}

晨跑

由于每个点(除了1,n)只能经过一次,所以考虑拆点,然后连一条容量为1的边,跑最小费用最大流。

一个点只可以选一次,可以尝试拆点,连容量为1的边。

#include<bits/stdc++.h>
#pragma GCC optimize(2) using namespace std; const int N = 500000, oo = 0x3f3f3f3f; int tot = 1, n, m, s, t, mincost;
int head[N], nxt[N], ver[N], edge[N], cost[N], d[5000];
bool vis[5000]; void add(int u, int v, int w, int c) {
ver[++tot] = v;
edge[tot] = w;
cost[tot] = c;
nxt[tot] = head[u];
head[u] = tot;
} bool spfa() {
memset(d, 0x3f, sizeof d);
memset(vis, 0, sizeof vis);
queue<int> q; q.push(s);
vis[s] = 1; d[s] = 0;
while (q.size()) {
int u = q.front(); q.pop(); vis[u] = 0;
for (int i = head[u]; i; i = nxt[i]) {
if (edge[i] and d[ver[i]] > d[u] + cost[i]) {
d[ver[i]] = d[u] + cost[i];
if (!vis[ver[i]])
q.push(ver[i]), vis[ver[i]] = 1;
}
}
}
return d[t] != d[0];
} int dinic(int u, int flow) {
if (u == t) return flow;
int k, rest = flow;
vis[u] = 1;
for (int i = head[u]; i and rest; i = nxt[i]) {
if ((!vis[ver[i]] or ver[i] == t) and edge[i] and d[ver[i]] == d[u] + cost[i]) {
k = dinic(ver[i], min(edge[i], rest));
if (!k) d[ver[i]] = 0;
edge[i] -= k;
edge[i ^ 1] += k;
rest -= k;
mincost += cost[i] * k;
}
}
return flow - rest;
} int main() {
scanf("%d%d", &n, &m);
s = 1; t = n + n;
add(1, n + 1, +oo, 0), add(n + 1, 1, 0, 0);
add(n, n + n, +oo, 0), add(n + n, n, 0, 0);
for (int i = 2; i < n; ++ i)
add(i, i + n, 1, 0), add(i + n, i, 0, 0);
for (int i = 1; i <= m; ++ i) {
int x, y, z; scanf("%d%d%d", &x, &y, &z);
add(x + n, y, 1, z); add(y, x + n, 0, -z);
}
int maxflow = 0;
while (spfa()) {
vis[t] = 1;
while (vis[t]) {
memset(vis, 0, sizeof vis);
maxflow += dinic(s, +oo);
}
}
cout << maxflow << " " << mincost << endl;
}

[SCOI2007]修车

对于一个修车工先后修 \(1\) ~ \(n\) 的车,对答案的贡献就是:

\(W_n × 1 + W_{n-1} × 2 + \dots + W_1 × n\)

所以我们考虑将每个工人拆成 \(n\) 个阶段的点,然后建二分图

对于每台车(左部节点)

它可以被这些工人中任意一位,任意一时刻被修理

则从这台车 \(x\) 连向右边的工人 \(i\) 的第 \(j\) 个阶段

对答案的贡献就是 \(W(i,x) * j\)

跑最小费用最大流即可

在直接建图不好算答案的时候不妨算贡献。

对于一个点有不同阶段,拆点的思想一定要有。

#include<bits/stdc++.h>
#define id(i,j) (i-1) * n + j
#pragma GCC optimize(2)
using namespace std;
const int N = 400000, oo = 0x3f3f3f3f;
int tot = 1, n, m, s, t;
int ver[N], nxt[N], edge[N], cost[N], head[N];
int d[N], vis[N], mincost, maxflow;
void add(int u, int v, int w, int c) {
ver[++tot] = v;
edge[tot] = w;
cost[tot] = c;
nxt[tot] = head[u];
head[u] = tot;
ver[++tot] = u;
edge[tot] = 0;
cost[tot] = -c;
nxt[tot] = head[v];
head[v] = tot;
}
int spfa() {
queue<int> q;
memset(vis, 0, sizeof vis);
memset(d, 0x3f, sizeof d);
d[s] = 0; vis[s] = 1; q.push(s);
while (q.size()) {
int u = q.front(); q.pop(); vis[u] = 0;
for (int i = head[u]; i; i = nxt[i]) {
int v = ver[i];
if (edge[i] and d[v] > d[u] + cost[i]) {
d[v] = d[u] + cost[i];
if (!vis[v]) {
vis[v] = 1;
q.push(v);
}
}
}
}
return d[t] != d[N - 3];
}
int dinic(int u, int flow) {
if (u == t) return flow;
int k, rest = flow;
vis[u] = 1;
for (int i = head[u]; i and rest; i = nxt[i]) {
int v = ver[i];
if ((!vis[v] or v == t) and edge[i] and d[v] == d[u] + cost[i]) {
k = dinic(v, min(edge[i], rest));
if (!k) d[v] = 0;
rest -= k;
edge[i] -= k;
edge[i ^ 1] += k;
mincost += k * cost[i];
}
}
return flow - rest;
}
int main() {
scanf("%d%d", &m, &n);
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j) {
int x; scanf("%d", &x);
for (int k = 1; k <= n; ++ k) {
add(i, n + id(j, k) , 1, x * k);
}
}
s = 0; t = n * m + n + 1;
for (int i = 1; i <= n; ++ i)
add(s, i, 1, 0);
for (int i = 1; i <= m; ++ i)
for (int j = 1; j <= n; ++ j)
add(n + id(i,j), t, 1, 0);
while (spfa()) {
vis[t] = 1;
while (vis[t]) {
memset(vis, 0, sizeof vis);
maxflow += dinic(s, +oo);
}
}
printf("%.2lf\n", 1.0 * mincost / n);
}

[NOI2012]美食节

[SCOI2007]修车 的加强版

考虑到 \(spfa\) 是基于边数的,且每次做一次spfa最多只能找到一条增广路

所以想到优化边数

对于一个厨师第 \(i\) 阶段做第 \(j\) 道菜, 对于同一 \(j\) 发现费用是随 \(i\) 单增的,

所以包含这个厨师做 \(j\) 这个菜的增广路找到的顺序一定是 \((i,j)\) , \((i+1, j)\) , \((i+2, j)\dots\)

所以我们每跑完一次 \(spfa\) 再加 \(i+1\) 的那条边,优化了边数,就可以 \(AC\) 了。

网络流如果复杂度过不去,要考虑适时加边,优化边数。

详见代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define N 120000
#define inf 0x3f3f3f3f
int tot = 1, n, m, s, t, sum, ans;
int ver[N], edge[N], cost[N], nxt[N], incf[N], d[N], vis[N], pre[N], head[N];
void add(int u, int v, int w, int c) {
ver[++tot] = v; edge[tot] = w; cost[tot] = c; nxt[tot] = head[u]; head[u] = tot;
ver[++tot] = u; edge[tot] = 0; cost[tot] = -c;nxt[tot] = head[v]; head[v] = tot;
}
int p[N], c[3000][3000];
int Ek() {
memset(d, 0x3f, sizeof d); memset(vis, 0, sizeof d); pre[t] = 0;
d[s] = 0; vis[s] = 1; queue<int> q; q.push(s);
incf[s] = inf;
while (q.size()) {
int u = q.front(); q.pop(); vis[u] = 0;
for (int i = head[u]; i; i = nxt[i]) {
int v = ver[i];
if (edge[i] and d[v] > d[u] + cost[i]) {
d[v] = d[u] + cost[i]; pre[v] = i;
incf[v] = min(incf[u], edge[i]);
if (!vis[v])
vis[v] = 1, q.push(v);
}
}
}
if (!pre[t]) return 0;
for (int u = t; u != s; u = ver[pre[u] ^ 1]) {
int e = pre[u];
edge[e] -= incf[t];
edge[e ^ 1]+= incf[t];
ans += incf[t] * cost[e];
}
return 1;
}
int dish[N], cook[N];
int main() {
ios::sync_with_stdio(0);
cin >> n >> m;//n种菜,m个厨师
for (int i = 1; i <= n; ++ i) cin >> p[i], sum += p[i];//sum个阶段
s = 0, t = N - 2;
for (int i = 1; i <= n; ++ i)
add(s, i + sum * m, p[i], 0);//对于每种菜,从源点连一条容量为该种菜数量的边
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j) {
cin >> c[i][j];
add(i + sum * m, (j - 1) * sum + 1, 1, c[i][j]);
//第一阶段连边
}
for (int i = 1; i <= m; ++ i)
add((i - 1) * sum + 1, t, 1, 0);//向汇点连边
for (int i = 1; i <= m; ++ i)
for (int j = 1; j <= sum; ++ j) {
int tmp = (i - 1) * sum + j;
dish[tmp] = j; cook[tmp] = i;//一个映射: tmp这个点是第j个菜(阶段), 由i这个厨师来做
}
while (Ek()) {
int tmp = ver[pre[t] ^ 1];
add(tmp + 1, t, 1, 0);//下个阶段连边
for (int i = 1; i <= n; ++ i)
add(i + m * sum, tmp + 1, 1, c[i][cook[tmp]] * (dish[tmp] + 1));
}
cout << ans << endl;
}

[ZJOI2010]网络扩容

第一问裸的最大流,

第二问考虑将每条路都扩容,

就在原图的基础上每个点再加一条容量为 \(inf\),有费用的边;

表示可以无线扩容,每扩容一点流量,就花费一点价值;

为了限制扩容的流量为 \(k\),从 \(n \xRightarrow{(k,0)} n+1, t \leftarrow n+1\);

从 \(s\) 到 \(t\) 跑最小费用最大流即可

网络流重边:一条边没有费用,一条边有费用,可以表示当一个东西超出某一限制后才会产生费用。

#include<bits/stdc++.h>
using namespace std;
const int N = 60000, inf = 0x3f3f3f3f;
int n, m, incf[N], s, t, a[N], b[N], c[N];
int tot = 1, head[N];
int ver[N], edge[N], cost[N], nxt[N];
bool vis[N];
int pre[N];
void add(int u, int v, int w, int c) {
ver[++tot] = v;
edge[tot] = w;
cost[tot] = c;
nxt[tot] = head[u];
head[u] = tot;
ver[++tot] = u;
edge[tot] = 0;
cost[tot] = -c;
nxt[tot] = head[v];
head[v] = tot;
}
int d[N];
int bfs() {
memset(d, 0, sizeof d);
queue<int> q; q.push(s);
d[s] = 1;
while (q.size()) {
int u = q.front(); q.pop();
for (int i = head[u]; i; i = nxt[i]) {
int v = ver[i];
if (edge[i] and !d[v]) {
d[v] = d[u] + 1;
if (v == t) return 1;
q.push(v);
}
}
}
return 0;
}
int mincost = 0;
int dinic(int u, int flow) {
if (u == t) return flow;
int k, rest = flow;
for (int i = head[u]; i and rest; i = nxt[i]) {
int v = ver[i];
if (edge[i] and d[v] == d[u] + 1) {
k = dinic(v, min(edge[i], rest));
if (!k) d[v] = 0;
rest -= k;
edge[i] -= k;
edge[i ^ 1] += k;
}
}
return flow - rest;
}
int Dinic(int u, int flow) {
if (u == t) return flow;
int k, rest = flow;
vis[u] = 1;
for (int i = head[u]; i and rest; i = nxt[i]) {
int v = ver[i];
if ((!vis[v] or v == t) and edge[i] and d[v] == d[u] + cost[i]) {
k = Dinic(v, min(edge[i], rest));
if (!k) d[v] = 0;
rest -= k;
edge[i] -= k;
edge[i ^ 1] += k;
mincost += k * cost[i];
}
}
return flow - rest;
}
int spfa() {
memset(d, 0x3f, sizeof d);
memset(vis, 0, sizeof vis);
queue<int> q; q.push(s);
int M = d[0];
d[s] = 0; vis[s] = 1;
while (q.size()) {
int u = q.front(); q.pop(); vis[u] = 0;
for (int i = head[u]; i; i = nxt[i]) {
int v = ver[i];
if (edge[i] and d[v] > d[u] + cost[i]) {
d[v] = d[u] + cost[i];
if (!vis[v])
q.push(v), vis[v] = 1;
}
}
}
return d[t] < M;
}
int zkw() {
while (spfa()) {
vis[t] = 1;
while (vis[t]) {
memset(vis, 0, sizeof vis);
Dinic(s, inf);
}
}
return mincost;
}
int z[N];
int main() {
ios::sync_with_stdio(0);
int k;
cin >> n >> m >> k;
for (int i = 1; i <= m; ++ i) {
cin >> a[i] >> b[i] >> c[i] >> z[i];
add(a[i], b[i], c[i], 0);
}
s = 1, t = n;
int ans1 = 0, flow;
while (bfs())
while (flow = dinic(s, inf))
ans1 += flow;
cout << ans1 << " ";
t ++; tot = 1;
memset(head, 0, sizeof head);
for (int i = 1; i <= m; ++ i) {
add(a[i], b[i], c[i], 0);
add(a[i], b[i], inf, z[i]);
}
add(t - 1, t, k + ans1, 0);
cout << zkw() << endl;
}

网络流小结(HNOI2019之前)的更多相关文章

  1. 网络流小结+[jzyzoj p1320] patrol

    一个不能更清楚的网络流介绍 ↑虽然不是我写的但是观摩一下总是没问题的嗯   看到晗神学的是神奇的ek算法. 但是看起来还是Ford-Fulkerson比较简单..所以我就学了这个...嗯其他的先看看. ...

  2. [bzoj1565][NOI2009]植物大战僵尸_网络流_拓扑排序

    植物大战僵尸 bzoj1565 题目大意:给你一张网格图,上面种着一些植物.你从网格的最右侧开始进攻.每个植物可以对僵尸提供能量或者消耗僵尸的能量.每个植物可以保护一个特定网格内的植物,如果一个植物被 ...

  3. [bzoj1497][NOI2006]最大获利_网络流_最小割

    最大获利 bzoj-1497 题目大意:可以建立一个点,花费一定的代价:将已经建立的两个点之间连边,得到一定收益.有些节点之间是不允许连边的. 注释:1<=点数<=5,000,1<= ...

  4. [luogu1402]酒店之王_网络流

    酒店之王 luogu-1402 题目大意:有n个人,p道菜,q个房间,每个人喜欢吃一些菜.喜欢住一些房间,如果一个人即住到了他喜欢的房间有吃到了他喜欢的菜,就对答案贡献++,求最大贡献. 注释:1&l ...

  5. [jdoj1817]Drainage Ditches_网络流

    Drainage Ditches jdoj-1817 题目大意:网络流裸求最大流 注释:n(点数),m(边数)<=200. 想法:裸的网络流求最大流,用bfs+dfs,美其名曰dinic. 没有 ...

  6. 网络流 最大流—最小割 之SAP算法 详解

    首先引入几个新名词: 1.距离标号: 所谓距离标号 ,就是某个点到汇点的最少的弧的数量(即边权值为1时某个点到汇点的最短路径长度). 设点i的标号为level[i],那么如果将满足level[i]=l ...

  7. 【做题】POJ3469 Dual Core CPU——第一道网络流

    刚学了Dinic就开始做题,然后就崩了. 题意:若干个任务,可以放在两个CPU中任意一个上完成,各有一定代价.其中又有若干对任务,如果它们不在同一个CPU上完成,会产生额外代价.最小化并输出代价. 一 ...

  8. OI网络流 简单学习笔记

    持续更新! 基本上只是整理了一下框架,具体的学习给出了个人认为比较好的博客的链接. ..怎么说呢,最基础的模板我就我不说了吧qwq,具体可以参考一下这位大佬写的博客:最大流,最小割,费用流 费用流 跑 ...

  9. 序列/树上差分小结 By cellur925

    首先我们需要注意一下的是,差分比较适用于修改比较多而查询比较少的情况. 一.序列上差分 借教室  这是一道二分答案,在check函数中用到差分技巧的一道题,譬如说我们要把一个序列中[l,r]区间都加上 ...

随机推荐

  1. Linux API fork 子进程 创建 & 无名管道通信

    #include<unistd.h> #include<stdio.h> int main() { ]; ]; pipe(filedes); printf("my p ...

  2. Linux Shell常用技巧(九)

    十九.  和系统运行进程相关的Shell命令:       1.  进程监控命令(ps):    要对进程进行监测和控制,首先必须要了解当前进程的情况,也就是需要查看当前进程,而ps命令就是最基本同时 ...

  3. 非法字符:“\ufeff”

    解决方法 将编码格式UTF-8+BOM文件转为普通的UTF-8文件. 用Notepad++打开文件,将编码从UTF-8+BOM改为UTF-8

  4. zookeeper启动时报错:Error contacting service. It is probably not running问题

    查看zookeeper.out发现启动日志报错未找到java路径. 启动日志位于zookeeper-4.0.10/bin目录下 修改/etc/profile中环境变量得以解决.

  5. TXT文件转换成DataSet数据集

    /// <summary> /// TXT文件转换成DataSet数据集 /// </summary> /// <param name="FilePath&qu ...

  6. 第五章 C程序结构

    一.数值类型 1.实数常量的表示:3.5(双精度),3.5f(单精度),3.5L(长双精度) 2.整数常量:char字符常量(‘a’,‘b’,‘0’)当做一个整型常量参加运算 3.数字字符与英文字母字 ...

  7. 【深度优先搜索】NOIP2017_D2T1 洛谷3958奶酪

    这道题的写法大体有两种:大法师DFS和并查集,两种算法都不难,本篇博客主要讲解DFS,而且测试数据特水,连个剪枝都不用都可以过. 题目描述[luogu传送门] 现有一块大奶酪,它的高度为 h,它的长度 ...

  8. SpringBoot 启动报The Java Virtual Machine has not been configured to use the desired default character encoding (UTF-8)

    解决方法: 启动的时候在VM中添加 Dfile.encoding=UTF-8 就好了!

  9. [BZOJ1565][NOI2009]植物大战僵尸-[网络流-最小割+最大点权闭合子图+拓扑排序]

    Description 传送门 Solution em本题知识点是用网络流求最大点权闭合子图. 闭合图定义:图中任何一个点u,若有边u->v,则v必定也在图中. 建图:运用最小割思想,将S向点权 ...

  10. [BZOJ1492][NOI2007]cash-[cdq分治]

    Description 传送门 Solution 首先,最优情况一定是某一天把所有金券卖出或买入是最优的. 在金券一定的情况下,分散卖一定没有统一在最优的那天卖更优. 然后,我们假定在某一天卖,则在该 ...