[Notes] 网络流扩展模型
Dinic 代码
int n, m, in[N], out[N];
struct Edge { int to, next, cap; } e[M];
int head[N], cur[N], cnt;
I void add(int u, int v, int cap) {
e[cnt] = Edge{v, head[u], cap};
head[u] = cnt++;
}
I void addedge(int u, int v, int cap) {
add(u, v, cap); add(v, u, 0);
}
int dis[N];
queue<int> q;
bool bfs(int s, int t) {
memset(dis, -1, sizeof dis);
memcpy(cur, head, sizeof head);
dis[s] = 0;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; ~i; i = e[i].next) {
int v = e[i].to;
if (~dis[v] || e[i].cap == 0) continue;
dis[v] = dis[u]+1;
q.push(v);
}
}
return dis[t] != -1;
}
int augment(int u, int t, int inflow) {
if (u == t) return inflow;
int outflow = 0;
for (int i = cur[u]; ~i && inflow-outflow; i = e[i].next) {
int v = e[i].to; cur[u] = i;
if (e[i].cap > 0 && dis[v] == dis[u]+1) {
int flow = augment(v, t, min(inflow-outflow, e[i].cap));
if (flow) {
e[i].cap -= flow;
e[i^1].cap += flow;
outflow += flow;
}
}
}
return outflow;
}
int dinic(int s, int t) {
int maxflow = 0, x = 0;
while (bfs(s, t)) {
while ((x = augment(s, t, INF))) maxflow += x;
}
return maxflow;
}
最大权闭合子图
定义
给定有向图 \(G=(V,E)\),点有点权,边无边权,选择点集 \(V'\subseteq V\),设 \(V'\) 内点的所有出边构成集合 \(E'\),若子图 \(G'=(V',E')\) 构成 \(G\) 的导出子图 \(G[V']\),且点集 \(V'\) 的权值和 \(W=\sum_{u\in V'}w_u\) 最大,称子图 \(G'\) 为图 \(G\) 的最大权闭合子图。
算法
如果有向图中有环需 \(\text{toposort}\) 处理掉。
转换为最小割模型。
连边:
原 \(\text{DAG}\) 上的边保留并将容量设置为 \(\text{INF}\)。
令 \(sum=\sum_{i=1}^{n}w_i\cdot[w_i\geq 0]\),答案即为 \(rst=sum-maxflow(s,t)\)。
最大密度子图
定义
给定无向图 \(G=(V,E)\),选择点集 \(V'\subseteq V\),对于 \(G\) 的导出子图 \(G[V']=(V',E')\),若子图密度 \(g=\frac{|E'|}{|V'|}\) 最大,则称子图 \(G[V']\) 为图 \(G\) 的最大密度子图。
算法
由于密度为分数,首先考虑分数规划。二分密度 \(g'\),定义 \(h(g')=\max_{V'\subseteq V}(|E'|-g'\cdot |V'|)\),若 \(h(g')>0\),则 \(g>g'\);反之,\(g\leq g'\)。
方法一
最粗暴的想法即对二分后的问题转为最大权闭合子图求解。一条边选择与否取决于两个端点,因此对边建点,按照依赖关系连向两个端点。
方法二
依然考虑转换为最小割模型,这样则需要将 \(|E'|\) 与割边数量建立联系,注意到:
其中 \(d\) 为度数数组,\(cnt[V',\overline{V'}]\) 为 \(V'\) 与 \(\overline{V'}\) 之间边的数量。
代入 \(h(g)\) 得,
此时与最小割建立联系,有:
由于 \(2g-d_u\) 可能为负数,设置偏移量 \(U=|E|\)。
根据式子建图:
- \(G[V']\) 内部边在网络图上连容量为 \(1\) 的双向边。
- \(\forall u\in V'\),\(s\) 向 \(u\) 连容量为 \(2g-d_u+U\) 的单向边。
- \(\forall u\in V'\), \(u\) 向 \(t\) 连容量为 \(U\) 的 单向边。
容易得到 \(h(g)=-\frac{maxflow(s,t)-|V|\cdot U}{2}\)。
混合图欧拉回路
定义
给定图 \(G=(V,E)\),若 \(E\) 中即存在有向边,又存在无向边,则称图 \(G\) 为混合图。
有向图欧拉回路存在性
若 \(\forall u\in V\),有 \(in_u=out_u\),则有向图 \(G=(V,E)\) 存在欧拉回路。其中 \(in,out\) 分别为入度数组和出度数组。
注意到这与网络流图的流量守恒是类似的。
算法
首先将无向边任意定向,此时每个点的出入度不一定都相等(即流量不守恒)。
引入源汇 \(s,t\) 调整度数差量,连边:
原本定向为 \(<a,b>\) 的无向边连边为:\(a\rightarrow b: cap = 1\)。
建图后从 \(s\) 到 \(t\) 跑最大流。
算法思想与 \(\text{HLPP}\) 预流推进类似。
上下界网络流
无源汇上下界可行流
由于下界较难满足,先通过预推流满足下界,此时流量不一定守恒。
推流后剩下的残量加入网络流图中。
为满足流量守恒,同样引入源汇 \(s,t\) 调整盈余流量,设盈余流量 \(\delta_u=out_u-in_u\),连边:
可以发现,无源汇上下界可行流的算法本质上与混合图欧拉回路是一致的。
有源汇上下界可行流
从给定汇点向给定源点连容量为 \(\text{INF}\) 的边,即可将有源汇问题转换为无源汇问题。
有源汇上下界最大流
求解一遍可行流后,删除所有附加的点和边,在残余网络上从给定源点向给定汇点跑最大流扩流。
int main() {
scanf("%d%d", &n, &m);
memset(head, -1, sizeof head);
int s = 0, t = n+1;
for (int i = 1; i <= m; i++) {
int u, v, l, r;
scanf("%d%d%d%d", &u, &v, &l, &r);
out[u] += l; in[v] += l;
addedge(u, v, r-l);
}
for (int i = 1; i <= n; i++) {
if (in[i] > out[i]) addedge(s, i, in[i]-out[i]);
else addedge(i, t, out[i]-in[i]);
}
addedge(n, 1, INF);
dinic(s, t);
s = 1, t = n;
int rst = e[cnt-1].cap;
e[cnt-2].cap = e[cnt-1].cap = 0;
rst += dinic(s, t);
printf("%d", rst);
return 0;
}
有源汇上下界最小流
预推流的下界流量不计入残量,因此在预推流后的网络上任意扩流推流均依然满足下界限制。
与最大流类似的,求解可行流后删除所有附加的点和边,在残余网络上从给定汇点向给定源点跑最大流退流。
int main() {
scanf("%d%d", &n, &m);
memset(head, -1, sizeof head);
int s = 0, t = n+1;
for (int i = 1; i <= m; i++) {
int u, v, l, r;
scanf("%d%d%d%d", &u, &v, &l, &r);
out[u] += l; in[v] += l;
addedge(u, v, r-l);
}
int sum = 0;
for (int i = 1; i <= n; i++) {
if (in[i] > out[i]) { addedge(s, i, in[i]-out[i]); sum += in[i]-out[i]; }
else addedge(i, t, out[i]-in[i]);
}
addedge(n, 1, INF);
dinic(s, t);
s = n, t = 1;
int rst = e[cnt-1].cap;
e[cnt-2].cap = e[cnt-1].cap = 0;
rst -= dinic(s, t);
printf("%d", rst);
return 0;
}

浙公网安备 33010602011771号