模板
网络流模板
大概原理是建边时建一条流量为0的反向边,可以用来反悔,选择一条路径后,路径上原来的边减去流量,反向边加上流量,不断找有没有更大的答案即可,dinic用dfs优化了一下,一般不会被卡。
最小割,最少去掉的边的值使得源点汇点不相连,最小割
=
=
= 最大流
//点从1-n 包括源点汇点 边从2开始编号 方便异或
//一次建好两条边
const int INF = 0x3f3f3f3f;
const int N = 1e5+7;
const int MAXN = 1e5+7;
int cnt = 1, n, st, ed, d[N], cur[N], head[N];
int T, r, c;
struct Edge
{
int to, w, nxt;
}e[MAXN<<1];
void AddEdge(int f, int t, int w)
{
e[++cnt].nxt = head[f]; e[cnt].to = t; e[cnt].w = w; head[f] = cnt;
e[++cnt].nxt = head[t]; e[cnt].to = f; e[cnt].w = 0; head[t] = cnt;
}
bool BFS()
{
rep(i, 1, n) d[i] = 0;
queue <int> q;
q.push(st); d[st] = 1;
while(!q.empty())
{
int u = q.front(); q.pop();
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if(d[v] || !e[i].w) continue;
q.push(v); d[v] = d[u] + 1;
}
}
rep(i, 1, n) cur[i] = head[i];
return d[ed] != 0;
}
int DFS(int u, int flow)
{
if(u == ed) return flow;
for(int i = cur[u]; i; i = e[i].nxt)
{
cur[u] = i;
int v = e[i].to;
if(d[v] != d[u]+1 || !e[i].w) continue;
int det = DFS(v, min(flow, e[i].w));
if(!det) continue;
e[i].w -= det; e[i^1].w += det;
return det;
}
return 0;
}
LL Dinic()
{
LL maxflow = 0, det;
while(BFS())
while(det = DFS(st, INF))
maxflow += det;
return maxflow;
}
void init()
{
cnt = 1;
rep(i, 1, n)
head[i] = 0;
}
费用流模板
最小费用最大流,每条边有费用
c
c
c,当流过
w
w
w时,花费
c
×
w
c\times w
c×w 的费用,求最大流的基础上,最少的费用。
把DFS换成了SPFA,因为有负边要用SPFA。
最大费用最大流就是把边建成负权,输出
(
−
1
)
×
a
n
s
(-1) \times ans
(−1)×ans
最小费用流,不考虑流的多少,那直接每次SPFA若发现
d
i
s
[
t
]
≥
0
dis[t] \geq 0
dis[t]≥0 直接结束就行。
最大费用流,同上。
//最小费用最大流 如果求最大费用,建边cost赋值为负数,输出费用*(-1)
const int INF = 0x3f3f3f3f;
const int N = 1e4+7;
const int MAXN = 1e5+7;
int T, n, head[N], cnt = 1, dis[N], pre[N], preve[N];
struct Edge
{
int f, to, w, c, nxt;
}e[MAXN];
//一次建两条边 cnt从1开始
void AddEdge(int f, int t, int w, int c)
{
e[++cnt].to = t; e[cnt].w = w; e[cnt].c = c; e[cnt].nxt = head[f]; head[f] = cnt;
e[++cnt].to = f, e[cnt].w = 0; e[cnt].c =-c; e[cnt].nxt = head[t]; head[t] = cnt;
}
bool SPFA(int s, int t)
{
int inq[N];
rep(i, 1, n)
dis[i] = INF, inq[i] = 0, pre[i] = -1;
dis[s] = 0;
queue <int> q;
q.push(s); inq[s] = 1;
while(!q.empty())
{
int u = q.front(); q.pop(); inq[u] = 0;
for(int i = head[u]; i; i = e[i].nxt)
{
if(e[i].w > 0)
{
int v = e[i].to, cost = e[i].c;
if(dis[u] + cost < dis[v])
{
dis[v] = dis[u] + cost;
pre[v] = u; preve[v] = i;
if(!inq[v])
{
q.push(v); inq[v] = 1;
}
}
}
}
}
return dis[t] != INF;
}
LL MinCost(int s, int t)
{
LL cost = 0; //maxflow = 0;
while(SPFA(s, t))
{
/* 最小费用流
if(dis[t] >= 0)
break;
*/
int v = t, flow = INF;
while(pre[v] != -1)
{
int u = pre[v], i = preve[v];
flow = min(flow, e[i].w);
v = u;
}
v = t;
while(pre[v] != -1)
{
int u = pre[v], i = preve[v];
e[i].w -= flow;
e[i^1].w += flow;
v = u;
}
cost += (LL)dis[t] * flow; //maxflow += flow;
}
return cost;
}
void init()
{
cnt = 1;
rep(i, 1, n)
head[i] = pre[i] = preve[i] = dis[i] = 0;
}
习题
网络流费用流题目难在转化问题,一般都很难直接看出来。
偷宝石 (最小割)
有 n n n个宝石, m m m个保安,每个宝石可能被若干个保安监视着,每个宝石有 a i a_i ai的价值,贿赂一个保安要 b i b_i bi,求可以获得的最大价值。
思考
如果想要么拿要么不拿,就不对了,因为这样走拿宝石和贿赂保安是对立的,我们考虑,假如全部都要拿,那么我们可以选择放弃一些宝石,或者贿赂保安,这样选择都是失去的价值。
建图
源点连宝石,保安连汇点。宝石和对应的保安相连。
考虑最小割,因为要么不拿这个宝石,要么把保安都贿赂了,要么割源点到宝石的边,权值就是宝石的价值,要么割保安到汇点的边,权值就是贿赂的钱,保安和宝石的关系不能破坏,权值为无穷大。
割掉若干边,使得源点到汇点没有路径就满足条件了,那么求出来最小割就是最少要放弃的值。
K取方格数 (费用流+拆点)
从左上角走到右下角,每次走右或者下,走到一个位置获得当然位置的值,但是只能获得一次,求最多能获得的值。
思考
费用流问题,重点在费用和建图,最大流就是走的K趟。
考虑源点到起点建容量为K费用为0的边,终点到汇点建容量为K,费用为0的边。
那么中间走的过程怎么建边,只能选一个怎么建边。
考虑拆点,把一个点拆为两个,第一个到第二个连两条边,一条容量为1,费用为点权,代表只能选一次,得到这么大价值,再连一条容量无穷大,费用为0,代表再走就没有价值了。再把所有能走的点之间连上容量无穷价值为0的边,跑最大费用最大流即可。
代码
//inexorable and inevitable
#include <bits/stdc++.h>
using namespace std;
#pragma GCC optimize (Ofast)
#define fastio ios_base::sync_with_stdio(0); cin.tie(NULL);
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define LL long long
#define mp make_pair
#define pb push_back
#define fr first
#define se second
#define endl "\n"
#define debug1 cout << "???" << endl;
#define debug2(x) cout << #x << ": " << x << endl;
const int INF = 0x3f3f3f3f;
const int N = 1e4+7;
const int MAXN = 1e5+7;
int T, n, head[N], cnt = 1, dis[N], pre[N], preve[N], a[101][101], k;
struct Edge
{
int f, to, w, c, nxt;
}e[MAXN];
void AddEdge(int f, int t, int w, int c)
{
e[++cnt].to = t; e[cnt].w = w; e[cnt].c = c; e[cnt].nxt = head[f]; head[f] = cnt;
e[++cnt].to = f, e[cnt].w = 0; e[cnt].c =-c; e[cnt].nxt = head[t]; head[t] = cnt;
}
bool SPFA(int s, int t)
{
int inq[N];
rep(i, 1, n)
dis[i] = INF, inq[i] = 0, pre[i] = -1;
dis[s] = 0;
queue <int> q;
q.push(s); inq[s] = 1;
while(!q.empty())
{
int u = q.front(); q.pop(); inq[u] = 0;
for(int i = head[u]; i; i = e[i].nxt)
{
if(e[i].w > 0)
{
int v = e[i].to, cost = e[i].c;
if(dis[u] + cost < dis[v])
{
dis[v] = dis[u] + cost;
pre[v] = u; preve[v] = i;
if(!inq[v])
{
q.push(v); inq[v] = 1;
}
}
}
}
}
return dis[t] != INF;
}
LL MinCost(int s, int t)
{
LL cost = 0; //maxflow = 0;
while(SPFA(s, t))
{
int v = t, flow = INF;
while(pre[v] != -1)
{
int u = pre[v], i = preve[v];
flow = min(flow, e[i].w);
v = u;
}
v = t;
while(pre[v] != -1)
{
int u = pre[v], i = preve[v];
e[i].w -= flow;
e[i^1].w += flow;
v = u;
}
cost += (LL)dis[t] * flow; //maxflow += flow;
}
return cost;
}
void init()
{
cnt = 1;
rep(i, 1, n)
head[i] = pre[i] = preve[i] = dis[i] = 0;
}
int main()
{
fastio
// freopen("in.txt", "r", stdin);
cin >> T;
while(T--)
{
int r, c, s, t;
cin >> r >> c >> k;
n = r*c*2+2, s = r*c*2+1, t = r*c*2+2;
init();
rep(i, 1, r)
{
rep(j, 1, c)
{
cin >> a[i][j];
int u = (i-1) * c + j;
AddEdge(u*2-1, u*2, INF, 0);
AddEdge(u*2-1, u*2, 1, -a[i][j]);
if(j < c)
AddEdge(u*2, u*2+1, INF, 0);
if(i < r)
AddEdge(u*2, (u+c)*2-1, INF, 0);
}
}
AddEdge(s, 1, k, 0);
AddEdge(r*c*2, t, k, 0);
cout << MinCost(s, t) * (-1) << endl;
}
return 0;
}
越流越贵(拆边)
最小费用最大流,但是一条边流过花费
w
i
2
w_i^2
wi2的价值。
思考
考虑拆边,因为边最大流量较小,拆为
w
i
w_i
wi条边,边权为
n
2
n^2
n2的差分数列,所有类似单调的费用流都可以拆边做。一般最大流量都比较小。
代码
const int INF = 0x3f3f3f3f;
const int N = 1e3+7;
const int MAXN = 1e5+7;
int m, T, n, head[N], cnt = 1, dis[N], pre[N], preve[N], sq[101], t[101];
struct Edge
{
int f, to, w, c, nxt;
}e[MAXN];
//一次建两条边 cnt从1开始
void AddEdge(int f, int t, int w, int c)
{
e[++cnt].to = t; e[cnt].w = w; e[cnt].c = c; e[cnt].nxt = head[f]; head[f] = cnt;
e[++cnt].to = f, e[cnt].w = 0; e[cnt].c =-c; e[cnt].nxt = head[t]; head[t] = cnt;
}
bool SPFA(int s, int t)
{
int inq[N];
rep(i, 1, n)
dis[i] = INF, inq[i] = 0, pre[i] = -1;
dis[s] = 0;
queue <int> q;
q.push(s); inq[s] = 1;
while(!q.empty())
{
int u = q.front(); q.pop(); inq[u] = 0;
for(int i = head[u]; i; i = e[i].nxt)
{
if(e[i].w > 0)
{
int v = e[i].to, cost = e[i].c;
if(dis[u] + cost < dis[v])
{
dis[v] = dis[u] + cost;
pre[v] = u; preve[v] = i;
if(!inq[v])
{
q.push(v); inq[v] = 1;
}
}
}
}
}
return dis[t] != INF;
}
pair<LL, LL> MinCost(int s, int t)
{
LL cost = 0, maxflow = 0;
while(SPFA(s, t))
{
int v = t, flow = INF;
while(pre[v] != -1)
{
int u = pre[v], i = preve[v];
flow = min(flow, e[i].w);
v = u;
}
v = t;
while(pre[v] != -1)
{
int u = pre[v], i = preve[v];
e[i].w -= flow;
e[i^1].w += flow;
v = u;
}
cost += (LL)dis[t] * flow; maxflow += flow;
}
return mp(cost, maxflow);
}
void init()
{
cnt = 1;
rep(i, 1, n)
head[i] = pre[i] = preve[i] = dis[i] = 0;
}
int main()
{
fastio
// freopen("in.txt", "r", stdin);
rep(i, 1, 100)
sq[i] = i * i, t[i] = sq[i] - sq[i-1];
cin >> T;
while(T--)
{
cin >> n >> m;
init();
int u, v, w;
rep(i, 1, m)
{
cin >> u >> v >> w;
rep(j, 1, w) AddEdge(u, v, 1, t[j]);
}
pair <LL, LL> ans = MinCost(1, n);
cout << ans.se << ' ' << ans.fr << endl;
}
return 0;
}
序列选数(网络流思想,贪心+可反悔)
给一个序列,选
k
k
k个数,若选了一个数就不能选它两边的了,求最大和。
思考
显然嗯贪心是不行的,我们考虑可以反悔,因为若选了一个数,如果之后不选它,说明一定是选了它两边的数,那么我们每选一个数,把这三个数更新为一个数,值是
a
i
−
1
+
a
i
+
1
−
a
i
a_{i-1} + a_{i+1} - a_{i}
ai−1+ai+1−ai,每次选最大的即可。
注意!
如果选了当前最左边或者最右边的位置(有一边没数了),这个时候就不能像之前一样,因为如果这样更新当前这个数,再选它不是多选了,而是没变,因为是一个数变成一个数。
这个时候如果选了端点的位置,说明端点位置大于旁边有数那个,一定不会再选有数的那个了,把两个都设置为不会再选。
两种做法
Ⅰ用优先队列,优先队列存位置,cmp是对应的数组的值比较。
Ⅱ用树状数组维护区间最大值,有坑,初始化为-INF,因为可能会有负数,两边的数的和小于中间的时候。
struct cmp
{
bool operator()(const int aa, const int bb)
{
return a[aa] < a[bb];
}
};
priority_queue <int, vector <int>, cmp> pq;