P4843题解
P4843题解
基础
- 网络流(不会的可以搜索,或者看看本蒟蒻的blog,这里略过)
- 上下界网络流相关问题(这个不会没有关系,我下面讲)
建模
一到比较裸的有源汇上下界最小流。每条边必走一次,要求求出最小的流量。由于比较裸,这里当作上下界流的例题讲。
什么是有源汇上下界最小流
顾名思义,就是在最大流的基础上增加了边的最小经过流量,使得整个网络可行,并且找出最小流量的方案。为了简化问题,我们先从无源汇上下界可行流说起。
求解无源汇可行流
一个比较朴实的想法就是将一个边的最大限制减去最小限制,跑最大流即可。但是该方法并不满足流量守恒定律,因此做出一下调整。
我们在图中新建两个虚拟点,设置为新的虚拟源汇点 \(S\)、\(T\)。设 \(d_u\) 为点 \(u\) 所有入度边中的流量下限与所有出度中的流量下限之差。接下来我们对 \(d_u\) 与 \(0\) 的大小关系进行分类讨论。
- 若 \(d_u>0\) 则将该点与新源点 \(S\) 连接,流量为 \(d_u\)。
- 若 \(d_u<0\) 则将该点与新汇点 \(T\) 连接,流量为 \(-d_u\)。
- 若 \(d_u==0\),我们不需要考虑,反正不管怎么连流量都是 \(0\)。
为什么要这么连呢,我们可以分析一下网络流的过程,由于我们直接连接的是流量上限 \(r\) 与 流量下限 \(l\) 的差作为该边的流量的,这会导致本来可以在 \(l\) 以内退流退掉的流量被清空了(可以回忆一下找增广路的退流过程)。我们对某一点 \(u\) 的所有 \(l\) 情况进行汇总,记录一个 \(d_u\) 来表示所有入度边中的流量下限与所有出度中的流量下限之差,可以理解为判断该点在总体上是流入还是流出,以此来弥补未能正确退流的流量。
注意:在原图中的原来的源汇点也需要与新的源汇点进行连接。
有源汇上下界可行流
有无源汇的最大区别就是源汇点是否满足流量守恒,我们在处理无源汇问题的时候不需要考虑源汇点的流量是否守恒,默认源点能无限产生,汇点能无限吸收。但在有源汇问题中考虑了源汇点的流量守恒性,不过处理起来也非常简单,我们只要让源汇点并入网络,也就是让汇点向源点连一条流量为无穷的边即可。
有源汇上下界最大流
这个其实也很好处理,在跑完一边有源汇上下界可行流之后记录当前的最大流量(即汇点向源点的流量),判断一下当前网络流是否为满流,若不是,则无解,否这断开汇点向源点的边,跑一次无源汇上下界可行流,答案即为当前流加上之前记录的最大流。
有源汇上下界最小流
和最大流类似,只不过是先处理无源汇上下界可行流,此时就已经将该网络中的所以增广路全部跑满,然后再连接汇点向源点的边,此时的最大流便是满足上下界要求的最小流。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
const int M = 2e4 + 10;
const int inf = 0x3f3f3f3f;
int cnt = 1;
int head[N];
vector<int>g[N];
struct edge
{
int u, v, c, val, nxt;
edge(int u = 0, int v = 0, int c = 0, int val = 0, int nxt = 0) : u(u), v(v), c(c), val(val), nxt(nxt) {}
} e[M];
void ADD(int u, int v, int c, int val)
{
cnt++;
e[cnt] = edge(u, v, c, val, head[u]);
head[u] = cnt;
}
void add_edge(int u, int v, int c, int val)
{
ADD(u, v, c, val);
ADD(v, u, 0, -val);
}
int d[N];
void add_limit(int u, int v, int l, int r)
{
add_edge(u, v, r - l, 0);
d[u] -= l;
d[v] += l;
}
int n;
int dep[N];
int now[N];
bool vis[N];
int s, t;
bool bfs()
{
memset(dep, 0, sizeof dep);
dep[s] = 1;
queue<int> q;
q.push(s);
while (!q.empty())
{
int u = q.front();
now[u] = head[u];
q.pop();
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].v;
int c = e[i].c;
if (dep[v] || !c)
continue;
dep[v] = dep[u] + 1;
q.push(v);
}
}
return dep[t] != 0;
}
int dfs(int u, int flow)
{
if (u == t)
return flow;
vis[u] = 1;
int nowflow = 0;
for (int i = now[u]; i && nowflow < flow; i = e[i].nxt)
{
now[u] = i;
int v = e[i].v;
int c = e[i].c;
if (dep[v] != dep[u] + 1 || !c || vis[v])
continue;
int ff = dfs(v, min(flow - nowflow, c));
if (ff)
nowflow += ff, e[i].c -= ff, e[i ^ 1].c += ff;
else
dep[v] = inf;
}
vis[u] = 0;
return nowflow;
}
int maxflow()
{
int ans = 0;
while (bfs())
{
int nowflow;
while ((nowflow = dfs(s, inf)))
ans += nowflow;
}
return ans;
}
int main()
{
cin >> n;
int ss = n + 1, tt = n + 2;
s = n + 3, t = n + 4;
for (int i = 1; i <= n; i++)
{
add_limit(ss, i, 0, inf);
add_limit(i, tt, 0, inf);
int k;
cin >> k;
for (int j = 1; j <= k; j++)
{
int v;
cin >> v;
add_limit(i, v, 1, inf);
}
}
for (int i = 1; i <= tt; i++)
{
if (d[i] > 0)
add_edge(s, i, d[i], 0);
else if (d[i] < 0)
add_edge(i, t, -d[i], 0);
}
maxflow();
add_limit(tt, ss, 0, inf);
maxflow();
cout << e[cnt].c;
}