内容纲要
标定
考虑每条边的流量范围:$b(u, v) \leq f(u, v) \leq c(u, v)$。
无源汇上下界可行流
首先我们给这条边标入最大限制 $c(u, v) - b(u, v)$,并默认认为该边已经流入了 $b(u, v)$ 的流量,然后我们可以给每个点设置一个 $M_i$ 来进行流量搜集。
如果:
- $M_i = 0$,那没啥问题。
- $M_i > 0$,说明下界要求流入的流量更多,所以我们从附加源点 $S$ 连到此点。
- $M_i < 0$,同理,说明下界要求流出的流量更多,所以我们专门开辟一个通道为此点流出至附加汇点 $T$。
代码:
// LOJ115.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 220, MAX_E = 20400;
int n, m, head[MAX_N], current, di[MAX_N], start_pos, end_pos, dep[MAX_N], cur[MAX_N], idx[MAX_E], lowers[MAX_E];
struct edge
{
int to, nxt, weight;
bool tag;
} edges[MAX_E << 1];
void addpath(int src, int dst, int weight)
{
edges[current].to = dst, edges[current].nxt = head[src];
edges[current].weight = weight, head[src] = current++;
}
void addtube(int src, int dst, int weight)
{
addpath(src, dst, weight);
addpath(dst, src, 0);
}
bool bfs()
{
memset(dep, 0, sizeof(dep));
queue<int> q;
q.push(start_pos), dep[start_pos] = 1;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = head[u]; i != -1; i = edges[i].nxt)
if (dep[edges[i].to] == 0 && edges[i].weight > 0)
dep[edges[i].to] = dep[u] + 1, q.push(edges[i].to);
}
return dep[end_pos] != 0;
}
int dfs(int u, int flow)
{
if (flow == 0 || u == end_pos)
return flow;
for (int &i = cur[u]; i != -1; i = edges[i].nxt)
if (dep[edges[i].to] == dep[u] + 1 && edges[i].weight > 0)
if (int di = dfs(edges[i].to, min(flow, edges[i].weight)))
{
edges[i].weight -= di, edges[i ^ 1].weight += di;
return di;
}
return 0;
}
int dinic()
{
int ret = 0;
while (bfs())
{
memcpy(cur, head, sizeof(head));
while (int di = dfs(start_pos, 0x3f3f3f3f))
ret += di;
}
return ret;
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d", &n, &m), start_pos = n + 1, end_pos = n + 2;
for (int i = 1, u, v, lower, upper; i <= m; i++)
{
scanf("%d%d%d%d", &u, &v, &lower, &upper);
idx[i] = current, addtube(u, v, upper - lower), di[u] -= lower, di[v] += lower, lowers[i] = lower;
}
for (int i = 1; i <= n; i++)
if (di[i] < 0)
addtube(i, end_pos, -di[i]);
else if (di[i] > 0)
addtube(start_pos, i, di[i]);
dinic();
bool flag = true;
for (int i = head[start_pos]; i != -1; i = edges[i].nxt)
flag &= (edges[i].weight == 0);
if (flag)
{
puts("YES");
for (int i = 1; i <= m; i++)
printf("%d\n", edges[idx[i] ^ 1].weight + lowers[i]);
}
else
puts("NO");
return 0;
}
有源汇上下界可行流
把有源汇上下界网络 $G$ 中的 $T$ 向 $S$ 连接一条无限大的渠道,转换为无源汇上下界可行进行求解。
addtube(end_pos, start_pos, 0x3f3f3f3f);
有源汇上下界最大流
其实有两种方法:
- 第一种:这个方法比较好理解,但是时间复杂度会比较大,大概是 $\Theta(\text{dinic}(n) \log_2 n)$。我们可以二分这个最大流,然后从 $T$ 到 $S$ 连一条下界为 $mid$、上界无穷大的的边,在有可行流的情况下保持最大流。
- 先按照「有源汇上下界可行流」的方法进行转换,先找一遍附加源点到附加汇点最大流,然后不管附加源点汇点,在原网络的参与网络上找 $S \to T$ 的最大流。不管附加源汇点其实可以直接把最后的那条 $T \to S$ 的边修改成 $0$ 边进行锁死,然后重新设置源汇点即可。复杂度为 $\Theta(\text{dinic}(n))$这个不是很好理解,可以看代码吧:
// LOJ116.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 330, MAX_E = 1e5 + 200, INF = 0x3f3f3f3f;
typedef long long ll;
int n, m, gS, gT, start_pos, end_pos, head[MAX_N], current, dep[MAX_N], cur[MAX_N], di[MAX_N];
struct edge
{
int to, nxt, weight;
} edges[MAX_E << 1];
void addpath(int src, int dst, int weight)
{
edges[current].to = dst, edges[current].weight = weight;
edges[current].nxt = head[src], head[src] = current++;
}
void addtube(int src, int dst, int weight)
{
addpath(src, dst, weight);
addpath(dst, src, 0);
}
bool bfs()
{
memset(dep, 0, sizeof(dep));
queue<int> q;
q.push(start_pos), dep[start_pos] = 1;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = head[u]; i != -1; i = edges[i].nxt)
if (dep[edges[i].to] == 0 && edges[i].weight > 0)
dep[edges[i].to] = dep[u] + 1, q.push(edges[i].to);
}
return dep[end_pos] != 0;
}
int dfs(int u, int flow)
{
if (u == end_pos || flow == 0)
return flow;
for (int &i = cur[u]; i != -1; i = edges[i].nxt)
if (edges[i].weight > 0 && dep[edges[i].to] == dep[u] + 1)
if (int di = dfs(edges[i].to, min(flow, edges[i].weight)))
{
edges[i].weight -= di, edges[i ^ 1].weight += di;
return di;
}
return 0;
}
ll dinic()
{
ll ret = 0;
while (bfs())
{
memcpy(cur, head, sizeof(cur));
while (int di = dfs(start_pos, 0x3f3f3f3f))
ret += di;
}
return ret;
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d%d%d", &n, &m, &gS, &gT);
start_pos = n + 1, end_pos = n + 2;
for (int i = 1, u, v, lower, upper; i <= m; i++)
{
scanf("%d%d%d%d", &u, &v, &lower, &upper);
addtube(u, v, upper - lower);
di[u] -= lower, di[v] += lower;
}
for (int i = 1; i <= n; i++)
if (di[i] < 0)
addtube(i, end_pos, -di[i]);
else
addtube(start_pos, i, di[i]);
addtube(gT, gS, INF);
ll ret = dinic();
bool flag = true;
for (int i = head[start_pos]; i != -1; i = edges[i].nxt)
flag &= edges[i].weight == 0;
if (flag == false)
puts("please go home to sleep"), exit(0);
ret = edges[current - 1].weight;
start_pos = gS, end_pos = gT;
edges[current - 1].weight = edges[current - 2].weight = 0;
ret += dinic(), printf("%lld\n", ret);
return 0;
}
有源汇上下界最小流
这个就很学玄学了。首先还是有两种解法:
- 二分出流量,然后建图的时候限制下即可。
- 先按照「有源汇上下界可行流」的方法进行转换,先找一遍附加源点到附加汇点最大流,然后不管附加源点汇点,在原网络的参与网络上找 $T \to S$ 的最大流(注意这个关系变了)。不管附加源汇点其实可以直接把最后的那条 $T \to S$ 的边修改成 $0$ 边进行锁死,然后重新设置源汇点即可。复杂度为 $\Theta(\text{dinic}(n))$这个不是很好理解,可以看代码吧:
// LOJ117.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 5e4 + 200, MAX_E = 3e5 + 200, INF = 0x3f3f3f3f;
typedef long long ll;
int n, m, gS, gT, start_pos, end_pos, head[MAX_N], current, dep[MAX_N], cur[MAX_N], di[MAX_N];
struct edge
{
int to, nxt, weight;
} edges[MAX_E << 1];
void addpath(int src, int dst, int weight)
{
edges[current].to = dst, edges[current].weight = weight;
edges[current].nxt = head[src], head[src] = current++;
}
void addtube(int src, int dst, int weight)
{
addpath(src, dst, weight);
addpath(dst, src, 0);
}
bool bfs()
{
memset(dep, 0, sizeof(dep));
queue<int> q;
q.push(start_pos), dep[start_pos] = 1;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = head[u]; i != -1; i = edges[i].nxt)
if (dep[edges[i].to] == 0 && edges[i].weight > 0)
dep[edges[i].to] = dep[u] + 1, q.push(edges[i].to);
}
return dep[end_pos] != 0;
}
int dfs(int u, int flow)
{
if (u == end_pos || flow == 0)
return flow;
for (int &i = cur[u]; i != -1; i = edges[i].nxt)
if (edges[i].weight > 0 && dep[edges[i].to] == dep[u] + 1)
if (int di = dfs(edges[i].to, min(flow, edges[i].weight)))
{
edges[i].weight -= di, edges[i ^ 1].weight += di;
return di;
}
return 0;
}
ll dinic()
{
ll ret = 0;
while (bfs())
{
memcpy(cur, head, sizeof(cur));
while (int di = dfs(start_pos, 0x3f3f3f3f))
ret += di;
}
return ret;
}
int main()
{
memset(head, -1, sizeof(head));
scanf("%d%d%d%d", &n, &m, &gS, &gT);
start_pos = n + 1, end_pos = n + 2;
for (int i = 1, u, v, lower, upper; i <= m; i++)
{
scanf("%d%d%d%d", &u, &v, &lower, &upper);
addtube(u, v, upper - lower);
di[u] -= lower, di[v] += lower;
}
for (int i = 1; i <= n; i++)
if (di[i] < 0)
addtube(i, end_pos, -di[i]);
else
addtube(start_pos, i, di[i]);
addtube(gT, gS, INF);
ll ret = dinic();
bool flag = true;
for (int i = head[start_pos]; i != -1; i = edges[i].nxt)
flag &= edges[i].weight == 0;
if (flag == false)
puts("please go home to sleep"), exit(0);
ret = edges[current - 1].weight;
start_pos = gT, end_pos = gS;
edges[current - 1].weight = edges[current - 2].weight = 0;
ret -= dinic(), printf("%lld\n", ret);
return 0;
}