目录
网络流是传说中大名鼎鼎的省选算法,想必不是一朝一夕能够学会的,谨以此文记录零基础的博主初次学习网络流的过程。
板块零:链式前向星
网络流的存图用vector<int> g[N]不方便,因为有反向边,是和正向边同时存入的,而邻接矩阵则是空间浪费大,遍历慢。复习一下链式前向星,这个东西在dijkstra堆优化算法里面用到过。
原料:
struct edge {
int to, w, next;//一个结构体
} g[maxn];
int head[maxn];
head[i]数组里面记录的是最后一次更新后的标号,to是终点,w是边权,next是上一个同起点的边。
加边:
void add_edge(int u, int v, int w) {
g[cnt].to = v;
g[cnt].w = w;
g[cnt].next = head[u];
head[u] = cnt++;
}
首先,cnt是标号,从零开始,head数组赋初值为-1便于后期处理。边权和方向不用考虑,我们需要注意的是,如何通过最后一次更新的值索引到结构体数组g的同起点的所有边例如。
存1 2 ,1 3, 1 4
cnt | head[1] | g[cnt].next |
0 | 0 | 0 -1(没有前驱) |
1 | 1 | 1 0 |
2 | 2 | 2 1 |
3 | 3 | 3 2 |
首先我们知道最后一个,head可以理解为线头,根据独立编号cnt索引前驱。直到-1,即使有其他的便加入,每一条边的编号是唯一确定的,对于任意一个边的编号,其next继承的是编号更新前的上一条同起点的边,然后更新head[]。
遍历边:
for(int i = 1; i <= n; i++)//n个起点
{
for(int j = head[i]; j != -1; j = edge[j].next)
{
}
}
对于每一个同起点的集合,取线头,线头处的结构体是有数据的,找前驱,当找到最后一个数的时候,前驱为-1,跳出循环。
板块一:最大流(Dinic算法)
概念:
1、反向边:反向边是用来反悔的,是可以虚拟出反向的流,和正向的流合并可以得到实际的真实流量。
2、残量网络:是剩余的可行的流的空间,管道的容量减去对应流(包括正向和反向的流)的大小。
3、增广流:找到一条从源点到终点的可行流,处处管道的残量都大于零,如果其中有虚拟的反向流的话可以原来的流合并,互相交换连接,保持原来的到终点的流不变并增加流,实现增广。
板子:
结构体:
struct Edge {
int from, to, cap, flow;//起点,终点,容量,流量
Edge() {}
Edge(int from, int to, int cap, int flow) {
this->from = from;
this->to = to;
this->cap = cap;
this->flow = flow;
}
};
预备:
Edge edge[1000000];//边数
int cnt, head[2000], deep[2000];//点数,deep存深度
初始化:
void init() {
cnt = 0;
memset(head, -1, sizeof(head));
}
加边:
void AddEdge(int u, int v, int c) {
edges[m] = Edge(u, v, c, 0);
nxt[m] = head[u];//nxt记录上一条边,heard记录同起点的最后一条边
head[u] = m++;
edges[m] = Edge(v, u, 0, 0);
nxt[m] = head[v];
head[v] = m++;
}
另一种写法:
注意,反向边刚开始是零
分层:BFS给图分层,找增广路的时候只能找下一层
bool dinic_bfs() //bfs方法构造层次网络
{
queue<int> q;
memset(level, 0, sizeof(level));
q.push(1);
level[1] = 1;
int u, v;
while (!q.empty()) {
u = q.front();
q.pop();
for (v = 1; v <= E; v++) {
if (!level[v] && edge[u][v].c>edge[u][v].f) {
level[v] = level[u] + 1;
q.push(v);
}
}
}
return level[E] != 0;
}
另一种用残量表示的写法似乎更简洁:
int bfs(int s, int t) {
queue<int> q;
memset(deep, 0, sizeof(deep));
q.push(s);
deep[s] = 1;
int top;
while (!q.empty()) {
top = q.front();
q.pop();
for (int i = head[top]; i != -1; i = edge[i].next) {
int v = edge[i].to, w = edge[i].w;
if (!deep[v] && w) {
deep[v] = deep[top] + 1;
q.push(v);
}
}
}
return deep[t];//只有当源点可以到达终点才可以
}
查找增广路:DFS
bool dfs(int u,int exp)
{
if(u==T)return exp;
int flow=0,tmp=0;
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(dis[v]==dis[u]+1&&edge[i].w>0)
{
tmp=dfs(v,min(exp,edge[i].w));
if(!tmp)continue;
exp-=tmp;
flow+=tmp;
edge[i].w-=tmp;
edge[i^1].w+=tmp; //i^1是加一或者减一
if(!exp)break;
}
}
return flow;
}
类似的写法:
int dfs(int s, int t, int inflow) {
if(s==t) return inflow;
int acflow=0;
for(int i=head[s]; i+1; i=edge[i].next) {
if(edge[i].w && deep[s]+1 == deep[edge[i].to]) {
int x = dfs(edge[i].to,t,min(inflow, edge[i].w));
if(x>0) {
acflow+=x; inflow-=x;edge[i].w -= x; edge[i^1].w += x;
if(!inflow) break;
}
}
}
if(!acflow) deep[s] = 0;//如果找不到可行流,令深度为零,等待下一次分层
return acflow;
}
当前弧优化:利用&引用变量改变会引起被引用变量改变的特性我们对查找增广路的过程进行当前弧优化,因为每次进行增广查找的时候都会直接榨干一条路,下次查找的时候直接从这里开始便可。
int DFS(int x, int a) {
if (x == t || a == 0) return a;
int flow = 0, f;
for (int& i = cur[x]; i != -1; i = nxt[i]) {
Edge& e = edges[i];
if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap-e.flow))) > 0) {
e.flow += f;
edges[i^1].flow -= f;
flow += f;
a -= f;
if (a == 0) break;
}
}
return flow;
}
Dinic:
int dinic() {
int sum=0, tf=0;
while (dinic_bfs()) {
while (tf = dinic_dfs(1, INF))
sum += tf;
}
return sum;
}
一直找到终点不在分层网络中,得最大流。
main函数框架:
init();
scanf("%d %d", &n, &m);
int l = inf, r = 0, sum = 0;
for(int i = 1;i <= n; ++i) {
scanf("%d %d %d", &p, &s, &e);
sum += p;
l = min(l, s); r = max(r, e);
add_edge(0, i, p);
for(int j = s;j <= e; j++) {
add_edge(i, j+n, 1);
}
}
int fin = r+n+1;
for(int i = l; i <= r; ++i) {
add_edge(i+n, fin, m);
}
printf("Case %d: %s\n\n", ++cas, dinic(0, fin)==sum?"Yes":"No");
由于Dinic算法过于复杂,还是封装起来好用,这是来自oiwiki的当前弧优化的板子,仔细作注解:
#define maxn 205
#define inf 0x3f3f3f3f
struct Edge {
int from, to, cap, flow;//起点,终点,容量,流量
Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
};
struct Dinic {
int n, m, s, t;//点数,边数,起点,终点
vector<Edge> edges;//存参数
vector<int> g[maxn];//存连通关系
int d[maxn], cur[maxn];//d是深度,cur是当前边
bool vis[maxn];
void init(int n) {
for (int i = 0; i < n; i++) g[i].clear();
edges.clear();
}
void add_edge(int from, int to, int cap) {
edges.push_back(Edge(from, to, cap, 0));
edges.push_back(Edge(to, from, 0, 0));
m = edges.size();//偶数
g[from].push_back(m - 2);//偶数
g[to].push_back(m - 1);//奇数
}
bool bfs() {
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(s);//推入源点
d[s] = 0;
vis[s] = 1;//标记起点
while (!q.empty()) {
int x = q.front();
q.pop();//g[x][i]是在edge数组里的边的标号
for (int i = 0; i < g[x].size(); i++) {
Edge e = edges[g[x][i]];
if (!vis[e.to] && e.cap > e.flow) {//如果没有访问过,而且有残量
vis[e.to] = 1;
d[e.to] = d[x] + 1;//深度加1
q.push(e.to);
}
}
}
return vis[t];//看看终点是否有在层次网络当中
}
int dfs(int x, int a) {//x是当前节点,a是当前可行流
if (x == t || a == 0) return a;
int flow = 0, f;
for (int &i = cur[x]; i < g[x].size(); i++) {
Edge &e = edges[g[x][i]];//更改地图
if (d[x] + 1 == d[e.to] && (f = dfs(e.to, min(a, e.cap - e.flow))) > 0) { //如果层次低到高,而且存在可行流
e.flow += f;
edges[g[x][i] ^ 1].flow -= f;
flow += f;//所有方向的可行流之和
a -= f;//当前可行行流减小
if (!a) break;
}
}
return flow;
}
int max_flow(int s, int t) {
this->s = s;
this->t = t;
int flow = 0;
while (bfs()) {
memset(cur, 0, sizeof(cur));
flow += dfs(s, inf);//源点到源点的可行流无穷大
}
return flow;//一直到无法分层为止
}
};
第一次学会结构体内的函数的调用。
题目:
第一题:纯粹的裸题
要用指针来调用函数,代码如下
#include <bits/stdc++.h>
using namespace std;
#define maxn 205
#define inf 0x3f3f3f3f
struct Edge {
int from, to, cap, flow;//起点,终点,容量,流量
Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
};
struct Dinic {
int n, m, s, t;//点数,边数,起点,终点
vector<Edge> edges;//存参数
vector<int> g[maxn];//存连通关系
int d[maxn], cur[maxn];//d是深度,cur是当前边
bool vis[maxn];
void init(int n) {
for (int i = 0; i < n; i++) g[i].clear();
edges.clear();
}
void add_edge(int from, int to, int cap) {
edges.push_back(Edge(from, to, cap, 0));
edges.push_back(Edge(to, from, 0, 0));
m = edges.size();//偶数
g[from].push_back(m - 2);//偶数
g[to].push_back(m - 1);//奇数
}
bool bfs() {
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(s);//推入源点
d[s] = 0;
vis[s] = 1;//标记起点
while (!q.empty()) {
int x = q.front();
q.pop();//g[x][i]是在edge数组里的边的标号
for (int i = 0; i < g[x].size(); i++) {
Edge e = edges[g[x][i]];
if (!vis[e.to] && e.cap > e.flow) {//如果没有访问过,而且有残量
vis[e.to] = 1;
d[e.to] = d[x] + 1;//深度加1
q.push(e.to);
}
}
}
return vis[t];//看看终点是否有在层次网络当中
}
int dfs(int x, int a) {//x是当前节点,a是当前可行流
if (x == t || a == 0) return a;
int flow = 0, f;
for (int &i = cur[x]; i < g[x].size(); i++) {
Edge &e = edges[g[x][i]];//更改地图
if (d[x] + 1 == d[e.to] && (f = dfs(e.to, min(a, e.cap - e.flow))) > 0) { //如果层次低到高,而且存在可行流
e.flow += f;
edges[g[x][i] ^ 1].flow -= f;
flow += f;//所有方向的可行流之和
a -= f;//当前可行行流减小
if (!a) break;
}
}
return flow;
}
int max_flow(int s, int t) {
this->s = s;
this->t = t;
int flow = 0;
while (bfs()) {
memset(cur, 0, sizeof(cur));
flow += dfs(s, inf);//源点到源点的可行流无穷大
}
return flow;//一直到无法分层为止
}
};
int main() {
Dinic *dinic = new Dinic;
int n, m;
while (cin >> m >> n) {
dinic->init(n);
while (m--) {
int a, b, c;
cin >> a >> b >> c;
dinic->add_edge(a, b, c);
}
cout << dinic->max_flow(1, n) << '\n';
}
delete dinic;
return 0;
}