P4843题解

P4843题解

基础

  1. 网络流(不会的可以搜索,或者看看本蒟蒻的blog,这里略过)
  2. 上下界网络流相关问题(这个不会没有关系,我下面讲)

建模

一到比较裸的有源汇上下界最小流。每条边必走一次,要求求出最小的流量。由于比较裸,这里当作上下界流的例题讲。

什么是有源汇上下界最小流

顾名思义,就是在最大流的基础上增加了边的最小经过流量,使得整个网络可行,并且找出最小流量的方案。为了简化问题,我们先从无源汇上下界可行流说起。

求解无源汇可行流

一个比较朴实的想法就是将一个边的最大限制减去最小限制,跑最大流即可。但是该方法并不满足流量守恒定律,因此做出一下调整。

我们在图中新建两个虚拟点,设置为新的虚拟源汇点 \(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;
}
posted @ 2023-07-21 15:00  mjsdnz  阅读(7)  评论(0编辑  收藏  举报