dinic
模板
int s, t;
int dep[N], head[N], cur[N], cnt = 1;
struct E{
int to, nxt, w;
}e[M << 1];
void add(int u, int v, int w)
{
e[++ cnt] = {v, head[u], w};
head[u] = cnt;
e[++ cnt] = {u, head[v], 0};
head[v] = cnt;
}
bool bfs()
{
memset(dep, 0, sizeof dep);
queue<int> q;
q.push(s); dep[s] = 1; cur[s] = head[s];
while(q.size())
{
int u = q.front(); q.pop();
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if(e[i].w && !dep[v])
{
q.push(v);
cur[v] = head[v];
dep[v] = dep[u] + 1;
if(v == t) return true;
}
}
}
return false;
}
LL dfs(int u = s, LL flow = inf)
{
if(u == t) return flow;
int left = flow;
for(int & i = cur[u]; i && left/*attention*/; i = e[i].nxt)
{
int v = e[i].to;
if(e[i].w && dep[v] == dep[u] + 1)
{
LL c = dfs(v, min(e[i].w, left));
if(!c) dep[v] = 0;
e[i].w -= c, e[i ^ 1].w += c, left -= c;
}
}
return flow - left;
}
LL dinic()
{
LL maxflow = 0;
while(bfs())
maxflow += dfs();
return maxflow;
}
407. 稳定的牛分配
题目链接
二分范围,枚举最小值,获取最大值即可。
note: 当前残存容量为0则不再枚举剩余连边。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <vector>
#include <map>
#include <queue>
#include <cmath>
#include <list>
#define LL int
using namespace std;
typedef pair<int, int> PII;
const int N = 1100, B = 30, M = N * 100, inf = 0x3f3f3f3f;
int s, t;
int dep[N], head[N], cur[N], cnt = 1;
struct E{
int to, nxt, w;
}e[M << 1];
void add(int u, int v, int w)
{
e[++ cnt] = {v, head[u], w};
head[u] = cnt;
e[++ cnt] = {u, head[v], 0};
head[v] = cnt;
}
bool bfs()
{
memset(dep, 0, sizeof dep);
queue<int> q;
q.push(s); dep[s] = 1; cur[s] = head[s];
while(q.size())
{
int u = q.front(); q.pop();
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if(e[i].w && !dep[v])
{
q.push(v);
cur[v] = head[v];
dep[v] = dep[u] + 1;
if(v == t) return true;
}
}
}
return false;
}
LL dfs(int u = s, LL flow = inf)
{
if(u == t) return flow;
int left = flow;
for(int & i = cur[u]; i && left/*attention*/; i = e[i].nxt)
{
int v = e[i].to;
if(e[i].w && dep[v] == dep[u] + 1)
{
LL c = dfs(v, min(e[i].w, left));
if(!c) dep[v] = 0;
e[i].w -= c, e[i ^ 1].w += c, left -= c;
}
}
return flow - left;
}
LL dinic()
{
LL maxflow = 0;
while(bfs())
maxflow += dfs();
return maxflow;
}
int n, b;
int a[N][B], c[B];
bool check(int mi, int mx)
{
for(int i = 0; i <= t; i ++) head[i] = 0;
cnt = 1;
for(int i = 1; i <= n; i ++) add(s, i, 1);
for(int i = 1; i <= b; i ++) add(i + n, t, c[i]);
for(int i = 1; i <= n; i ++)
for(int j = mi; j <= mx; j ++)
add(i, n + a[i][j], 1);
// cout <<"eeeeeee" << endl;
// for(int i = 2; i <= cnt; i += 2)
// {
// cout << e[i ^ 1].to << ' ' << e[i].to << endl;
// }
int flow = dinic();
// cout << mi << ' ' << mx << ' ' << flow << endl;
return flow == n;
}
int main()
{
scanf("%d%d", &n, &b);
s = 0, t = n + b + 1;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= b; j ++) scanf("%d", &a[i][j]);
for(int i = 1; i <= b; i ++) scanf("%d", &c[i]);
//枚举范围 再枚举最小值
int l = 1, r = b, range;
while(l < r)
{
range = (l + r) / 2;
// cout << "start " << range << ' ' << /l << " " << r << endl;
bool fl = false;
for(int mi = 1; mi + range - 1 <= b; mi ++)
{
int mx = mi + range - 1;
if(check(mi, mx))
{
fl = true;
break;
}
// cout << "mi " << mi << ' ' << "mx " << mx << endl;
}
if(fl) r = range;
else l = range + 1;
// cout << "end " << range << ' ' << l << ' ' << r << endl;
}
printf("%d", l);
return 0;
}
必经边/可行边/不可行边
380. 舞动的夜晚
题目链接
在二分图的最大匹配中:
- 必须边在所有最大匹配方案都会成为匹配边;
- 可行边是存在一个最大匹配方案使得其成为匹配边;
- 不可行边是在所有的最大匹配方案中都不存在的边。
此题要求所有不可行边。
3类边可用网络流求解:
- 必须边:是匹配边,并且端点在残存网络中不在一个强连通分量里
- 可行边:是匹配边,或者端点在残存网络中在一个强连通分量里
- 不可行边:不是匹配边,并且端点在残存网络中不在一个强连通分量里
#include<bits/stdc++.h>
#define LL int
using namespace std;
const int N = 20000 + 10, M = 2e6 + 10, inf = 0x3f3f3f3f;
int n, m, s, t, k;
int dep[N], head[N], cur[N], cnt = 1;
struct E{
int to, nxt, w;
}e[M];
int dfn[N], low[N], dfn_cnt, c[N], scc_cnt, stk[N], top, ans_cnt, ans[N];
bool ins[N];
void add(int u, int v, int w)
{
e[++ cnt] = {v, head[u], w};
head[u] = cnt;
e[++ cnt] = {u, head[v], 0};
head[v] = cnt;
}
bool bfs()
{
memset(dep, -1, sizeof dep);
memcpy(cur, head, sizeof cur);
queue<int> q;
q.push(s);
dep[s] = 0;
while(!q.empty())
{
int u = q.front(); q.pop();
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to, w = e[i].w;
if(dep[v] == -1 && w > 0)
{
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
return dep[t] != -1;
}
LL dfs(int u = s, LL flow = inf)
{
if(u == t) return flow;
LL left = flow;
for(int & i = cur[u]; i && left; i = e[i].nxt)
{
int v = e[i].to, w = e[i].w;
if(dep[v] == dep[u] + 1 && w > 0)
{
LL c = dfs(v, min(left, w));
left -= c, e[i].w -= c, e[i ^ 1].w += c;
}
}
return flow - left;
}
LL dinic()
{
LL maxflow = 0;
while(bfs())
maxflow += dfs();
return maxflow;
}
void tarjan(int u)
{
stk[++ top] = u;
ins[u] = true;
dfn[u] = low[u] = ++ dfn_cnt;
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if(e[i].w == 0) continue;
if(!dfn[v])
{
tarjan(v);
low[u] = min(low[v], low[u]);
}
else if(ins[v]) low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u])
{
++ scc_cnt;
int v;
do
{
v = stk[top --];
ins[v] = false;
c[v] = scc_cnt;
}while(v != u);
}
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
s = n + m + 2, t = n + m + 1;
for(int i = 1; i <= k; i ++)
{
int u, v; scanf("%d%d", &u, &v);
add(u, v + 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);
dinic();
for(int i = 1; i <= n + m + 2; i ++)
if(!dfn[i])
tarjan(i);
//不可行边:不是匹配边 并且不在一个强连通分量里
// for(int i = 2; i <= cnt; i += 2)
// printf("u = %d, v = %d, w = %d\n", e[i^1].to, e[i].to, e[i].w);
// for(int i = 1; i <= n + m; i ++) printf("i = %d, c[i] = %d\n", i, c[i]);
for(int i = 2; i <= k * 2 + 2; i += 2) //第2条边开始
{
int v = e[i].to, u = e[i ^ 1].to, w = e[i].w;
if(w && c[u] != c[v]) ans[++ ans_cnt] = i / 2;
}
if(!ans_cnt) printf("0\n\n");
else
{
printf("%d\n",ans_cnt);
for(int i = 1; i <= ans_cnt; i ++) printf("%d ", ans[i]);
}
return 0;
}
最小割
381. 有线电视网络
题目链接
使得原图不连通,即使得删点后至少某两个点无法连通,可枚举此两点为源点
s
s
s和汇点
t
t
t,求最小割后割边端点就是应该删除的点。但网络流求的是割边,因此可以将点
x
x
x拆成两个点
x
x
x和
x
+
n
x + n
x+n,在两个点之间连一条边,拆点可以将删点的操作变成割边的操作。
note: 边数和点数通过计算获取不要随便开大,特别是要memset,memcpy的时候。
#include<bits/stdc++.h>
#define LL int
using namespace std;
const int N = 100 + 10, M = 1e4 + 10, inf = 0x3f3f3f3f;
int n, m, s, t;
int dep[N], head[N], cur[N], cnt = 1;
struct E{
int to, nxt, w;
}e[M], e_[M];
void add(int u, int v, int w)
{
e_[++ cnt] = {v, head[u], w};
head[u] = cnt;
e_[++ cnt] = {u, head[v], 0};
head[v] = cnt;
}
bool bfs()
{
for(int i = 0; i < N; i ++) dep[i] = -1, cur[i] = head[i];
queue<int> q;
q.push(s);
dep[s] = 0;
while(!q.empty())
{
int u = q.front(); q.pop();
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to, w = e[i].w;
if(dep[v] == -1 && w > 0)
{
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
return dep[t] != -1;
}
LL dfs(int u = s, LL flow = inf)
{
if(u == t) return flow;
LL left = flow;
for(int & i = cur[u]; i && left; i = e[i].nxt)
{
int v = e[i].to, w = e[i].w;
if(dep[v] == dep[u] + 1 && w > 0)
{
LL c = dfs(v, min(left, w));
left -= c, e[i].w -= c, e[i ^ 1].w += c;
}
}
return flow - left;
}
LL dinic()
{
LL maxflow = 0;
while(bfs())
maxflow += dfs();
return maxflow;
}
void init()
{
cnt = 1;
for(int i = 0; i < N; i ++) head[i] = 0;
}
int main()
{
while(~scanf("%d %d", &n, &m))
{
init();
for(int i = 1; i <= n; i ++) add(i, i + n, 1);
for(int i = 1; i <= m; i ++)
{
char ch;
cin >> ch;
int u, v; scanf("%d,%d", &u, &v);
cin >> ch;
u ++, v ++;
//x为入点 x + n为出点 一条边从出点到入点
add(u + n, v, inf), add(v + n, u, inf);
}
int ans = inf;
for(s = 1 + n; s <= 2 * n; s ++)
{
for(t = 1; t <= n; t ++)
{
if(s - n == t) continue;
for(int i = 0; i < M; i ++) e[i] = e_[i];
ans = min(ans, dinic());
}
}
printf("%d\n", ans == inf ? n : ans);
}
return 0;
}
最大流最小费用
在取得最大流的前提下获得最小费用。在EK算法将 b f s bfs bfs求最短路的过程用 s p f a spfa spfa取代,同时 s p f a spfa spfa也可以求最长路,即可求最大费用。
382. K取方格数
题目链接
note: 将
c
n
t
cnt
cnt置1;计算点数和边数。
#include<bits/stdc++.h>
using namespace std;
const int M = 4e4 + 10 , N = 5010, inf=0x3f3f3f3f;
struct E{
int to, nxt, w, c;
}e[M << 1];
int n, m, s, t, cnt = 1;
bool vis[N];
int maxflow, maxcost, head[N], cur[N], dis[N], incf[N], pre[N];
void add(int u, int v, int w, int c)
{
e[++ cnt] = {v, head[u], w, c};
head[u] = cnt;
e[++ cnt] = {u, head[v], 0, -c};
head[v] = cnt;
}
bool spfa()
{
memset(dis, 0xcf, sizeof dis);
memset(vis, 0, sizeof vis);
queue<int> q;
q.push(s);
dis[s] = 0, vis[s] = true, incf[s] = inf;
while(!q.empty())
{
int u = q.front(); q.pop();
// cout << "u" << u << endl;
vis[u] = false;
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if(e[i].w && dis[v] < dis[u] + e[i].c)
{
dis[v] = dis[u] + e[i].c;
incf[v] = min(incf[u], e[i].w);
pre[v] = i;
if(!vis[v])
{
vis[v] = true;
q.push(v);
}
}
}
}
return dis[t] != 0xcfcfcfcf;
}
void MCMF()
{
while(spfa())
{
for(int i = t; i != s; i = e[pre[i] ^ 1].to) //i表示的是节点 表示当前的增广路径
{
// cout << "i" << i << endl;
e[pre[i] ^ 1].w += incf[t];
e[pre[i]].w -= incf[t];
}
maxflow += incf[t];
maxcost += incf[t] * dis[t];
}
}
inline int id(int x, int y, int k){return (x - 1) * n + y + k * n * n;}
int main()
{
int k;
cin >> n >> k;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
{
int value; cin >> value;
add(id(i, j, 0), id(i, j, 1), 1, value), add(id(i, j, 0), id(i, j, 1), k - 1, 0);
if(j < n) add(id(i, j, 1), id(i, j + 1, 0), k, 0);
if(i < n) add(id(i, j, 1), id(i + 1, j, 0), k, 0);
}
s = id(1, 1, 0), t = id(n, n, 1);
MCMF();
cout << maxcost << endl;
return 0;
}
P4016 负载平衡问题(费用流)
源点向大于平均值的点连边权为 a b s ( a [ i ] − a v e r ) abs(a[i] - aver) abs(a[i]−aver)的边,小于平均值的点向汇点连边权为 a b s ( a [ i ] − a v e r ) abs(a[i]-aver) abs(a[i]−aver)的边。相邻的点再连一条边权为 i n f inf inf的边。这样网络流就是 ∑ a [ i ] − a v e r \sum{a[i]-aver} ∑a[i]−aver, i i i为权值大于 a v e r aver aver的点。大点流出,小点接受,最后相当于每个点都剩下0。而在网络流中每个流量都伴随着一个运输代价。
//mcmf模板
int a[N];
int main()
{
scanf("%d", &n);
int sum = 0;
for(int i = 0; i < n; i ++) {scanf("%d", &a[i]); sum += a[i];}
sum /= n, t = n, s = n + 1;
for(int i = 0; i < n; i ++)
{
if(a[i] > sum) add(s, i, a[i] - sum, 0);
if(a[i] < sum) add(i, t, sum - a[i], 0);
add(i, (i - 1 + n) % n, inf, 1);
add(i, (i + 1) % n, inf, 1);
}
MCMF();
printf("%d\n", mincost);
return 0;
}