2018年4月

Steiner Tree

Steiner Tree

Problem

给定一个点集,求权值和最小的一个边集使得这些点联通。可以连接到这些点以外的点。

Solution

这个组合最优化问题是 NP-hard 的。

我们考虑使用状压来指数级别枚举。

令 $ f_{i,S}:= $ 包含点 $ i $ 且连接点集中的状态为 $ S $ 的最小花费,可见有以下转移:

$$ f_{i,S}\leftarrow \begin{cases} f_{i,s}+f_{i,S\setminus s} \\ f_{j,S}+w(i,j) & \text{if }(i,j) \in E \end{cases} $$

第二类情况是反直觉的。但只要注意到定义之中我们的 $ i $ 并不一定包含在 $ S $ 以内,就容易知道只有第一种情况是不完备的。

处理第一种情况只是简单的子集枚举 DP。

考虑处理第二种情况。在固定 $ S $ 的情况下满足三角形不等式,所以可以直接用最短路模型来搞。

BZOJ 2595

$ n \times m $ 的网格图,一些点是景点(设有 $ k $ 个),剩下是空地,每一个空地上有些志愿者。

选取最少的志愿者使得经典联通。$ n,m,k\le 10 $.

Sol.

直接按上述做法搞即可。

Code(116056 kb, 1456 ms)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
template<class T> void read(T& x)
{
    char c = getchar(); T p = 1, n = 0;
    while(c < '0' || c > '9') {if(c == '-') p = -1; c = getchar();}
    while(c >= '0' && c <= '9'){n = n * 10 + c - '0'; c = getchar();}
    x = p * n;
}
template<class T,class U>void read(T&x,U&y){read(x),read(y);}
template<class T,class U,class V>void read(T&x,U&y,V&z){read(x),read(y),read(z);}
const int maxn = 15, INF = 0x3f3f3f3f;
int a[maxn][maxn], dp[maxn][maxn][1 << maxn];
queue<pair<int, int> > Q;
bool inq[maxn][maxn];
const int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};
struct T
{
    int x, y, z;
    T(int a = -1, int b = -1, int c = -1){x = a, y = b, z = c;}
} pre[maxn][maxn][1 << maxn];
int n, m;
void go(int S)
{
    while(!Q.empty())
    {
        int ux = Q.front().first, uy = Q.front().second; Q.pop();
        for(int i = 0; i < 4; ++i) 
        {
            int vx = ux + dx[i], vy = uy + dy[i];
            if(vx < 0 || vx >= n || vy < 0 || vy >= m) continue;
            if(dp[vx][vy][S] > dp[ux][uy][S] + a[vx][vy])
            {
                dp[vx][vy][S] = dp[ux][uy][S] + a[vx][vy];
                pre[vx][vy][S] = T(ux, uy, S);
                if(!inq[vx][vy]) Q.push(make_pair(vx, vy)), inq[vx][vy] = true;
            }
        }
        inq[ux][uy] = false;
    }
}
bool vis[maxn][maxn];
void dfs(int i, int j, int s)
{
    vis[i][j] = true;
    T t = pre[i][j][s];
    if(t.x == -1 && t.y == -1) return;
    dfs(t.x, t.y, t.z);
    if(t.x == i && t.y == j) dfs(t.x, t.y, s - t.z);
}
int main()
{
    read(n, m);
    int k = 0;
    memset(dp, INF, sizeof dp);
    for(int i = 0; i < n; ++i) for(int j = 0; j < m; ++j)
    {
        read(a[i][j]);
        if(a[i][j] == 0) dp[i][j][1 << k] = 0, ++k;
    }
    int N = 1 << k;
    for(int S = 0; S < N; ++S) 
    {
        if(S == N - 1)
            int a = 0;
        for(int i = 0; i < n; ++i) for(int j = 0; j < m; ++j) 
        {
            for(int s = S & (S - 1); s; s = (s - 1) & S)
            {
                int k = dp[i][j][s] + dp[i][j][S - s] - a[i][j];
                if(k < dp[i][j][S]) dp[i][j][S] = k, pre[i][j][S] = T(i, j, s);
            }
            if(dp[i][j][S] < INF) Q.push(make_pair(i, j)), inq[i][j] = true;
        }
        go(S);
    }
    int x = -1, y = -1;
    for(int i = 0; i < n && x == -1; ++i) for(int j = 0; j < m && x == -1; ++j) if(a[i][j] == 0) x = i, y = j;
    dfs(x, y, N - 1);
    printf("%d\n", dp[x][y][N - 1]);
    for(int i = 0; i < n; ++i, puts("")) for(int j = 0; j < m; ++j)
        if(a[i][j] == 0) putchar('x');
        else if(vis[i][j]) putchar('o');
        else putchar('_');
    return 0;
}

BZOJ 4774

$ n $ 点 $ m $ 边,选出权值最小的一个边集使得 $ i,n-i+1,\forall1\le i\le d $ 联通。

$ n,m\le10^4,1\le d\le 4 $.

Sol.

把 ST 处理出来。

注意到 ST 可能存在配对的边不相连通的情况(其实应该叫做 Steiner Trees),所以还要在做一次子集 DP.

Code(41180 kb, 5904 ms)

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
template<class T> void read(T& x)
{
    char c = getchar(); T p = 1, n = 0;
    while(c < '0' || c > '9') {if(c == '-') p = -1; c = getchar();}
    while(c >= '0' && c <= '9'){n = n * 10 + c - '0'; c = getchar();}
    x = p * n;
}
template<class T,class U>void read(T&x,U&y){read(x),read(y);}
template<class T,class U,class V>void read(T&x,U&y,V&z){read(x),read(y),read(z);}
const int maxn = 10, maxm = 1e4 + 10, INF = 0x3f3f3f3f;
struct E{int v, n, w;}e[maxm << 1];
int h[maxm], ec = 1;
inline void add_edge(int u, int v, int w){e[ec] = (E){v, h[u], w}; h[u] = ec++;}
queue<int> Q; bool inq[maxm];
int f[maxm][1 << maxn], g[1 << maxn];
int main()
{
    int n, m, d, k = 0; read(n, m, d);
    memset(f, INF, sizeof f);
    for(int i = 1; i <= d; ++i) f[i][1 << k] = 0, ++k;
    for(int i = n; i > n - d; --i) f[i][1 << k] = 0, ++k;
    int u, v, w; while(m--) read(u, v, w), add_edge(u, v, w), add_edge(v, u, w);
    int N = 1 << k;
    for(int S = 1; S < N; ++S)
    {
        for(int u = 1; u <= n; ++u) 
        {
            for(int s = S & (S - 1); s; s = (s - 1) & S)
                f[u][S] = min(f[u][S], f[u][s] + f[u][S - s]);
            if(f[u][S] < INF) Q.push(u), inq[u] = true;
        }
        while(!Q.empty())
        {
            int u = Q.front(); Q.pop(); inq[u] = false;
            for(int i = h[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(f[v][S] > f[u][S] + e[i].w)
            {
                f[v][S] = f[u][S] + e[i].w;
                if(!inq[v]) Q.push(v), inq[v] = true;
            }
        }
    }
    memset(g, INF, sizeof g);
    N = 1 << d;
    for(int S = 1; S < N; ++S) for(int u = 1; u <= n; ++u) g[S] = min(g[S], f[u][S | (S << d)]);
    for(int S = 1; S < N; ++S) for(int s = S & (S - 1); s; s = (s - 1) & S) g[S] = min(g[S], g[s] + g[S - s]);
    printf("%d", g[N - 1] == INF ? -1 : g[N - 1]);
    return 0;
}

BZOJ 4006

给定一个图,有一些频道,包含一些点,要求频道内的点联通。求最小代价。

$ n \le 1000, m\le 3000, p \le 10 $.

Sol.

求 STs. 然后再对每一个频道枚举一次子集合并。

Code(129828kb, 22016ms)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
using namespace std;
template<class T> void read(T& x)
{
    char c = getchar(); T p = 1, n = 0;
    while(c < '0' || c > '9') {if(c == '-') p = -1; c = getchar();}
    while(c >= '0' && c <= '9'){n = n * 10 + c - '0'; c = getchar();}
    x = p * n;
}
template<class T,class U>void read(T&x,U&y){read(x),read(y);}
template<class T,class U,class V>void read(T&x,U&y,V&z){read(x),read(y),read(z);}
const int maxn = 1000 + 5, maxm = 3000 + 5, maxp = 10 + 5, INF = 0x3f3f3f3f;
struct E{int v, n, w;}e[maxm << 1];
int h[maxn], ec = 1;
inline void add_edge(int u, int v, int w){e[ec] = (E){v, h[u], w}; h[u] = ec++;}
queue<int> Q; bool inq[maxn];
map<int, int> mm;
int mc = 1;
int ma[1 << maxp], f[maxn][1 << maxp], g[1 << maxp];
int main()
{
    int n, m, p; read(n, m, p);
    int u, v, w; while(m--) read(u, v, w), add_edge(u, v, w), add_edge(v, u, w);
    memset(f, INF, sizeof f); //memset(ma, INF, sizeof ma);
    int c, d, k = 0; while(p--) read(c, d), (!mm[c] ? mm[c] = mc++ : true), f[d][1 << k] = 0, ma[1 << mm[c] - 1] |= 1 << k, k++;
    int N = 1 << k;
    for(int S = 1; S < N; ++S)
    {
        for(int u = 1; u <= n; ++u)
        {
            for(int s = S & (S - 1); s; s = (s - 1) & S) f[u][S] = min(f[u][S], f[u][s] + f[u][S - s]);
            if(f[u][S] < INF) Q.push(u);
        }
        while(!Q.empty())
        {
            int u = Q.front(); Q.pop(); inq[u] = false;
            for(int i = h[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(f[v][S] > f[u][S] + e[i].w)
            {
                f[v][S] = f[u][S] + e[i].w;
                if(!inq[v]) Q.push(v), inq[v] = true;
            }
        }
    }
    memset(g, INF, sizeof g);
    N = 1 << mc - 1;
    for(int S = 1; S < N; ++S) 
        ma[S] = ma[S - (S & -S)] | ma[S & -S];
    for(int S = 1; S < N; ++S) for(int i = 1; i <= n; ++i) 
        g[S] = min(g[S], f[i][ma[S]]);
    for(int S = 1; S < N; ++S) for(int s = S & (S - 1); s; s = (s - 1) & S)
        g[S] = min(g[S], g[s] + g[S - s]);
    printf("%d\n", g[N - 1]);
    return 0;
}

Network Flow(I) - Basic Max-Flow Algorithms

Network Flow(I) - Basic Max-Flow Algorithms

网络流系列笔记:基础最大流算法。

谁能想到一天前我还半会不会 Dinic 呢。

网络流中最基础的部分应该就是求最大流了吧。

按理说如果是讲义的话应该从基础概念入手。

我也有这么考虑过严格化搭建模型框架,但太麻烦了。

而且这只是我的专题复习笔记。默认的前置知识以我的储备为准咯。

增广路定理证明。

Ex: LOJ #101

以下可以视为解决本道例题。代码最后给出。

两大类算法:基于增广路和基于预流推进。

Edmonds-Karp: $ O(nm^2) $

不好意思忘了是什么了。

Dinic: $ O(n^2m) $

将原图分层。然后开始 DFS 寻找增广路,每次只走相邻层的边。由增广路定理得解为正确。

然后可见分层起到优化复杂度的效果。

Complexity

首先可知,DFS 退出的时候,可以增广的所有增广路都已完成。考虑每一次 $ t $ 的标号,若和上次一样,则意味着上次并未增广完。而且必不可能标号更小。从而 $ t $ 的标号严格递增,这样 $ O(n) $ 次阶段一定可以求完最大流。

然后看 DFS 的复杂度。好像可以发现就是 $ O(nm) $ 的,我还不知道怎么严格证。

总的就是 $ O(n^2m) $ 了。

优化

我们来考虑一些常数优化,不 wys 的那种。

  1. 当前流量为 $ 0 $ 的时候直接返回。
  2. 增广过程中,如果一个点的标号没有被修改过,那么遍历它的出边的时候无需从头遍历,从最后访问的边开始就可,因为可能这条边还有残余。

注意

按理来说,我使用前向星每次都需要把链表头 memcpy 一次给记录访问边的 cur 数组。

然后在 LOJ 那种有毒的范围会被卡到 TLE.

使用 vector 记录边下标反而能过。

这启发我们 memcpy 实际上是非常慢的,不比 memset . 所以这使我们使用 Dinic 的时候会有些顾虑。

ISAP(aka. Improved Shortest Augment Path): $ O(n^2m) $

我们发现 Dinic 每次重新计算标号特别傻。走了一遍增广路之后标号的变化是肯定跟增广路相关,且是某种意义上连续的,这启发我们使用这个性质。

我们考虑 DFS 过程中修改标号。

具体来说,用 $ d_i $ 表示编号为 $ i $ 的点到终点的距离,和 Dinic 类似,我们只沿 $ (u, v) : d_u = d_v+1 $ 这样的边增广。如果增广不了了那么这个点的标号就过时了,需要改为出边编号最小的加一。这样增广下去,当起点到终点距离超过 $ n $ 的时候意味着不存在增广路了,算法退出。

Complexity

复杂度上界与 Dinic 的分析相同。

优化

  1. 从终点反向 BFS 求出初始标号。 $ O(n+m) $.
  2. (GAP)如果出现断层,则必不存在增广路,此时算法退出,维护一个 gap 数组记录某深度的点个数,深度发生变化时检查是否变为 $0$ 即可。
  3. 每次更新标号不必重新求最短路。注意到最短路修改具有连续性,只需给标号加一。
  4. 同 Dinic.

备注

在朴素 Dinic、魔改 Dinic 和 HLPP 中对本题跑得最稳定和最快。

实践中也挺快的,写起来也挺短。

可能成为主力用途。

HLPP(aka. Highest Label Preflow Push): $ O(n^2\sqrt{m}) $

考虑怎么手算网络流。从源流到汇,遇到不够就减。这是预流推进

思想

每个结点视为维护一个水池。源水深无限。维护队列,把源推进去。取出一个点,沿边把水按流量推到相邻点,该点加入队列。

这即是主要思想。一个显而易见的弊端是,两个点来回推,进入死循环。

我们仿照分层图的标号,引入高度的概念。源高度为 $n $, 汇为 $ 0 $ ,其他初始为 $ 0 $ ,而且我们规定,水只往下一层流。即只推边 $ (u,v):d_u=d_v+1 $.

如果一个点有水,但是周围点都比他高,我们抬高这个点,因为高度连续,每次这样高度加一,一直到 $ n + 1 $ 流回源。

Complexity

将队列改为优先队列,每次取最高标号的点,即为高标推进。

(Tarjan & Goldberg, 1986) 证明了复杂度为 $ O(n^2\sqrt{m}) $.

优化

  1. (GAP)抬高 $ 1 $ 的时候,如果这个高度没有其他点,则直接将高度大于其的全部置为 $ n + 1 $ 回流到源,因为此时必不存在增广路。

备注

实践中跑得比 ISAP 慢。原因可能这个界很紧而 ISAP 的界很松。

Code

魔改 Dinic (14081, 151596)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
template<class T> void read(T& x)
{
    char c = getchar(); T p = 1, n = 0;
    while(c < '0' || c > '9') {if(c == '-') p = -1; c = getchar();}
    while(c >= '0' && c <= '9'){n = n * 10 + c - '0'; c = getchar();}
    x = p * n;
}
template<class T,class U>void read(T&x,U&y){read(x),read(y);}
template<class T,class U,class V>void read(T&x,U&y,V&z){read(x),read(y),read(z);}
const int maxm = 4e6 + 10, maxn = 1e6 + 10, INF = 0x3f3f3f3f;
struct E{int v, f, c;} e[maxm << 1];
int ec = 2;
vector<int> G[maxn];
inline void add_edge(int u, int v, int w)
{
    e[ec] = (E){v,  0, w}; G[u].push_back(ec++); 
    e[ec] = (E){u, 0, 0}; G[v].push_back(ec++);
}
int d[maxn];
int Q[maxn];
int s, t, n;
bool bfs()
{
    memset(d, INF, sizeof d);
    int head = 0, tail = 0; Q[tail++] = s; d[s] = 0;
    while(head != tail)
    {
        int u = Q[head++]; head %= maxn;
        for(int i = 0; i < G[u].size(); i++) 
        {
            E &ed = e[G[u][i]];
            if(ed.f < ed.c && d[ed.v] == INF)
            d[ed.v] = d[u] + 1, Q[tail++] = ed.v, tail %= maxn;
        }
    }
    return d[t] != INF;
}
int cur[maxn];
int dfs(int u, int a = INF)
{
    if(!a || u == t) return a;
    int flow = 0, f;
    for(int &i = cur[u]; i < G[u].size(); i++)
    {
        E &ed = e[G[u][i]];
        if(d[ed.v] == d[u] + 1 && (f = dfs(ed.v, min(a, ed.c - ed.f))) != 0)
        {
            ed.f += f, e[G[u][i] ^ 1].f -= f, flow += f, a -= f;
            if(!a) return flow;
        }
    }
    return flow;
}
long long ret;
long long dinic()
{
    ret = 0;
    while(bfs()){memset(cur, 0, sizeof cur); ret += dfs(s);}
    return ret;
}
int m, u, v, w;
int main()
{
    read(n, m), read(s, t);
    while(m--) read(u, v, w), add_edge(u, v, w);
    printf("%lld\n", dinic());
    return 0;
}

ISAP(10358, 192120)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
template<class T> void read(T& x)
{
    char c = getchar(); T p = 1, n = 0;
    while(c < '0' || c > '9') {if(c == '-') p = -1; c = getchar();}
    while(c >= '0' && c <= '9'){n = n * 10 + c - '0'; c = getchar();}
    x = p * n;
}
template<class T,class U>void read(T&x,U&y){read(x),read(y);}
template<class T,class U,class V>void read(T&x,U&y,V&z){read(x),read(y),read(z);}
const int maxn = 1e6 + 5, maxm = 5e6 + 5, INF = 0x3f3f3f3f;
struct E{int v, n; long long f, c; }e[maxm << 1];
int h[maxn], ec = 2;
inline void add_edge(int u, int v, long long w) {e[ec] = (E){v, h[u], 0, w}, h[u] = ec++, e[ec] = (E){u, h[v], 0, 0}, h[v] = ec++;}
int n, s, t;
queue<int> Q;
int d[maxn], gap[maxn], cur[maxn];
long long go(int u = s, long long a = INF)
{
    if(u == t || !a) return a;
    long long flow = 0;
    for(int &i = cur[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(d[u] == d[v] + 1)
    {
        int tmp = go(v, min(a, e[i].c - e[i].f));
        e[i].f += tmp, e[i ^ 1].f -= tmp, flow += tmp, a -= tmp;
        if(!a) return flow;
    }
    if(!(--gap[d[u]])) d[s] = n + 1;
    ++gap[++d[u]], cur[u] = h[u];
    return flow;
}
long long maxflow()
{
    ++gap[d[t] = 1];
    memcpy(cur, h, sizeof h);
    Q.push(t);
    while(!Q.empty())
    {
        int u = Q.front(); Q.pop();
        for(int i = h[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(!d[v])
            ++gap[d[v] = d[u] + 1], Q.push(v);
    }
    long long ret = go();
    while(d[s] <= n) ret += go();
    return ret;
}
int main()
{
    int m; read(n, m), read(s, t);
    int u, v, w; while(m--) read(u, v, w), add_edge(u, v, w);
    printf("%lld\n", maxflow());
    return 0;
}

HLPP(TLE)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
template<class T> void read(T& x)
{
    char c = getchar(); T p = 1, n = 0;
    while(c < '0' || c > '9') {if(c == '-') p = -1; c = getchar();}
    while(c >= '0' && c <= '9'){n = n * 10 + c - '0'; c = getchar();}
    x = p * n;
}
template<class T,class U>void read(T&x,U&y){read(x),read(y);}
template<class T,class U,class V>void read(T&x,U&y,V&z){read(x),read(y),read(z);}
const int maxn = 1e6 + 5, maxm = 4e6 + 5, INF = 0x3f3f3f3f;
struct E{int v, n, f, c; } e[maxm << 1];
int h[maxn], ec = 2;
inline void add_edge(int u, int v, int w){e[ec] = (E){v, h[u], 0, w}, h[u] = ec++, e[ec] = (E){u, h[v], 0, 0}, h[v] = ec++;}
int n, s, t;
struct P{int x, d; bool operator<(const P& rhs)const{return d < rhs.d;}};
priority_queue<P> Q;
int gap[maxn], d[maxn];
long long w[maxn];
int push(int u, int v, int i)
{
    int f = min(w[u], 1ll * e[i].c - e[i].f);
    e[i].f += f, e[i ^ 1].f -= f, w[u] -= f, w[v] += f;
    return f;
}
void Gap(int dd){for(int i = 1; i <= n; ++i) if(i != s && i != t && d[i] > dd && d[i] <= n) d[i] = n + 1;}
long long maxflow()
{
    d[s] = n, w[s] = INF; Q.push((P){s, d[s]});
    while(!Q.empty())
    {
        int u = Q.top().x; Q.pop();
        if(!w[u]) continue;
        for(int i = h[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if((u == s || d[u] == d[v] + 1) && push(u, v, i) && v != t && v != s)
            Q.push((P){v, d[v]});
        if(u != s && u != t && w[u])
        {
            if(!(--gap[d[u]])) Gap(d[u]);
            ++gap[++d[u]];
            Q.push((P){u, d[u]});
        }
    }
    return w[t];
}
int main()
{
    int m; read(n, m), read(s, t);
    int u, v, w; while(m--) read(u, v, w), add_edge(u, v, w);
    printf("%lld\n", maxflow());
    return 0;
}

Network Flow(II) - Some Extensions

Network Flow(II) - Some Extensions

无源汇有上下界可行流 (I)

我们已经会处理只带上界了的。考虑把下界转换掉。

我们建立超级源 $S$ 和超级汇 $T$ ,每条边权值变为 $ w_i:=high_i-low_i $. $ S $ 向每条边终点连流量为 $ low_i $ 的边, 每条边起点向 $ T $ 连流量为 $ low_i $ 的边。

此时 $ S $ 到 $ T $ 跑一边最大流,若 $ S $ 出边均满,则存在可行流。

我们视 $ T $ 与 $ S $ 间存在一条流量为 $ +\infty $ 的边,从而我们注意到每条边被拆成了两个部分:必要边即下界,通过超级源汇连接起来;附加边即上下界差,仍在原图位置上。

显而易见如此构造找到了可行流的充要条件:$ S,T $ 间需满流。但是这条边并不需要显式体现在算法过程中。

有源汇有上下界可行流

参照上节构造,加入 $ t \xrightarrow{+\infty} s $ 即可.

此时原图变为无源汇的循环流。

有源汇有上下界最大流

在跑完可行流以后,删除 $ S, T $ ,跑一次 $ s $ 到 $ t $ 的最大流。

答案显然就是第一次可行流 + 第二次最大流。

有源汇有上下界最小流

在跑完可行流以后,删除 $ S, T $ ,跑一次 $ t $ 到 $ s $ 的最大流。

答案并不显然就是第一次可行流 - 第二次最大流。

其实第二次的效果相等于把没必要的流全部退回去。

无源汇有上下界可行流 (II)

说网络流这种玄学算法……一定要注意常数……

优化连边什么的,让我们来看一下……

然后看到最开始的那个连边思路很tm的sb。一个点往超级源汇可能连了好多好多条边。

那就先提前统计,每个点 $ d_i=\sum low_j $ , 边 $ j $ 连到 $ i $ 贡献正,连出贡献负。

然后 $ d_i > 0 $ 则 $ S \xrightarrow{d_i}i $ , 否则 $ i \xrightarrow{-d_i} T $.

DEL 老年选手智商特别低下。

边数点数傻傻分不清。

源汇和超级源汇傻傻分不清。

边数起始值还能写错也是服气。

LOJ 115 (42, 1780)

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
template<class T> void read(T& x)
{
    char c = getchar(); T p = 1, n = 0;
    while(c < '0' || c > '9') {if(c == '-') p = -1; c = getchar();}
    while(c >= '0' && c <= '9'){n = n * 10 + c - '0'; c = getchar();}
    x = p * n;
}
template<class T,class U>void read(T&x,U&y){read(x),read(y);}
template<class T,class U,class V>void read(T&x,U&y,V&z){read(x),read(y),read(z);}
const int maxn = 200 + 10, maxm = 10200 + 10, INF = 0x3f3f3f3f;
struct E{int v, n; long long f, c;} e[maxm << 3];
int h[maxn], ec = 2;
inline void add_edge(int u, int v, long long w){e[ec] = (E){v, h[u], 0, w}; h[u] = ec++; e[ec] = (E){u, h[v], 0, 0}; h[v] = ec++;}
int n;
int d[maxn], cur[maxn], gap[maxn];
long long go(int u = 0, long long a = INF)
{
    if(u == n + 1 || !a) return a;
    long long flow = 0, f;
    for(int &i = cur[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(d[u] == d[v] + 1 && (f = go(v, min(a, e[i].c - e[i].f))))
    {
        e[i].f += f, e[i ^ 1].f -= f, flow += f, a -= f;
        if(!a) return flow;
    }
    if(!(--gap[d[u]])) d[0] = n + 3;
    ++gap[++d[u]]; cur[u] = h[u];
    return flow;
}
queue<int> Q;
long long maxflow()
{
    gap[d[n + 1] = 1] = 1;
    Q.push(n + 1);
    while(!Q.empty())
    {
        int u = Q.front(); Q.pop();
        for(int i = h[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(!d[v])
            ++gap[d[v] = d[u] + 1], Q.push(v);
    }
    memcpy(cur, h, sizeof h);
    long long ret = go();
    while(d[0] <= n + 2) 
        ret += go();
    return ret;
}
int l[maxm];
int main()
{
    int m; read(n, m);
    long long cnt = 0;
    for(int i = 1, u, v, r; i <= m; ++i) read(u, v), read(l[i], r), cnt += l[i], add_edge(0, v, l[i]), add_edge(u, v, r - l[i]), add_edge(u, n + 1, l[i]);
    if(maxflow() < cnt) puts("NO");
    else
    {
        puts("YES");
        for(int i = 1; i <= m; ++i) printf("%lld\n", l[i] + e[6 * i - 2].f);
    }
    return 0;
}

LOJ 116 (51, 2220)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
template<class T> void read(T& x)
{
    char c = getchar(); T p = 1, n = 0;
    while(c < '0' || c > '9') {if(c == '-') p = -1; c = getchar();}
    while(c >= '0' && c <= '9'){n = n * 10 + c - '0'; c = getchar();}
    x = p * n;
}
template<class T,class U>void read(T&x,U&y){read(x),read(y);}
template<class T,class U,class V>void read(T&x,U&y,V&z){read(x),read(y),read(z);}
const int maxn = 202 + 5, maxm = 9999 + 5, INF = 0x3f3f3f3f;
struct E{int v, n; long long f, c; } e[maxm << 3];
int h[maxn], ec = 2;
inline void add_edge(int u, int v, long long w){e[ec] = (E){v, h[u], 0, w}; h[u] = ec++; e[ec] = (E){u, h[v], 0, 0}; h[v] = ec++;}
int n, s, t, S, T;
int d[maxn], gap[maxn], cur[maxn];
long long cnt = 0;
long long go(int u, bool flag = false, long long a = INF)
{
    if(u == t || !a) return a;
    long long flow = 0, f;
    for(int &i = cur[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(d[u] == d[v] + 1 && (f = go(v, flag, min(a, e[i].c - e[i].f))))
    {
        e[i].f += f, e[i ^ 1].f -= f, flow += f, a -= f;
        if(!a) return flow;
    }
    if(!(--gap[d[u]])) d[s] = n + 3;
    ++gap[++d[u]], cur[u] = h[u];
    return flow;
}
void output()
{
    for(int i = 1; i <= n; ++i) for(int j = h[i], v = e[j].v; j; j = e[j].n, v = e[j].v) if(j % 2 == 0)
        printf("%d %d %d %d\n", i, v, e[j].f, e[j].c);
    puts("");
}
queue<int> Q;
long long maxflow(int ss, int tt)
{
    ++gap[d[n + 1] = 1];
    Q.push(n + 1);
    while(!Q.empty())
    {
        int u = Q.front(); Q.pop();
        for(int i = h[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(!d[v])
            ++gap[d[v] = d[u] + 1], Q.push(v);
    }
    memcpy(cur, h, sizeof h);
    s = 0, t = n + 1;
    long long ret = go(0);
    while(d[0] <= n + 2) ret += go(0);
    if(ret < cnt) return -1;
    // output();
    s = ss, t = tt, S = 0, T = n + 1;
    memset(gap, 0, sizeof gap); memset(d, 0, sizeof d);
    ++gap[d[tt] = 1];
    Q.push(tt);
    while(!Q.empty())
    {
        int u = Q.front(); Q.pop();
        for(int i = h[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(!d[v] && ((i % 2 == 1 && e[i^1].f < e[i^1].c) || (i % 2 == 0 && e[i].f < e[i].c)))
            ++gap[d[v] = d[u] + 1], Q.push(v);
    }
    memcpy(cur, h, sizeof h);
    ret = go(s, true, INF);
    // output();
    while(d[s] <= n + 2) ret += go(s, true, INF);
    return ret;
}
int main()
{
    int m, s, t; read(n, m); read(s, t);
    int u, v, l, r; while(m--) read(u, v), read(l, r), cnt += l, add_edge(0, v, l), add_edge(u, v, r - l), add_edge(u, n + 1, l);
    add_edge(t, s, INF);
    long long ret = maxflow(s, t);
    if(ret == -1) puts("please go home to sleep");
    else printf("%lld\n", ret);
    return 0;
}

LOJ 117 (1497, 18920)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
template<class T> void read(T& x)
{
    char c = getchar(); T p = 1, n = 0;
    while(c < '0' || c > '9') {if(c == '-') p = -1; c = getchar();}
    while(c >= '0' && c <= '9'){n = n * 10 + c - '0'; c = getchar();}
    x = p * n;
}
template<class T,class U>void read(T&x,U&y){read(x),read(y);}
template<class T,class U,class V>void read(T&x,U&y,V&z){read(x),read(y),read(z);}
const int maxn = 50003 + 5, maxm = 125003 + 5;
const long long INF = 0x3f3f3f3f3f3f3f;
struct E{int v, n; long long f, c; } e[maxm << 3];
int h[maxn], ec = 2;
inline void add_edge(int u, int v, long long w){e[ec] = (E){v, h[u], 0, w}; h[u] = ec++; e[ec] = (E){u, h[v], 0, 0}; h[v] = ec++;}
int n, s, t, S, T;
int d[maxn], gap[maxn], cur[maxn];
long long cnt = 0;
long long go(int u, long long a = INF)
{
    if(u == t || !a) return a;
    long long flow = 0, f;
    for(int &i = cur[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(d[u] == d[v] + 1 && (f = go(v, min(a, e[i].c - e[i].f))))
    {
        e[i].f += f, e[i ^ 1].f -= f, flow += f, a -= f;
        if(!a) return flow;
    }
    if(!(--gap[d[u]])) d[s] = n + 3;
    ++gap[++d[u]], cur[u] = h[u];
    return flow;
}
void output()
{
    for(int u = 0; u <= n + 1; ++u)
    for(int i = h[u], v = e[i].v; i; i = e[i].n, v = e[i].v)
        printf("%d %d %d %d\n", u, v, e[i].f, e[i].c);
}
queue<int> Q;
long long maxflow(int ss, int tt)
{
    ++gap[d[n + 1] = 1];
    Q.push(n + 1);
    while(!Q.empty())
    {
        int u = Q.front(); Q.pop();
        for(int i = h[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(!d[v])
            ++gap[d[v] = d[u] + 1], Q.push(v);
    }
    memcpy(cur, h, sizeof h);
    s = 0, t = n + 1;
    long long ret = 0;
    while(d[0] <= n + 2) ret += go(0);
    if(ret < cnt) return -1;
    long long t1 = -e[ec - 1].f; ret = 0;

    //output();
    s = tt, t = ss, S = 0, T = n + 1;
    h[s] = e[h[s]].n;
    h[t] = e[h[t]].n; ec -= 2;
    memset(gap, 0, sizeof gap); memset(d, 0, sizeof d);
    ++gap[d[t] = 1];
    Q.push(t);
    while(!Q.empty())
    {
        int u = Q.front(); Q.pop();
        for(int i = h[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(!d[v] && v != S && v != T && e[i^1].f < e[i^1].c)
            ++gap[d[v] = d[u] + 1], Q.push(v);
    }
    if(!d[s]) return t1 - ret;
    memcpy(cur, h, sizeof h);
    while(d[s] <= n + 1) ret += go(s);
    return t1 - ret;
}
int main()
{
    int m, s, t; read(n, m); read(s, t);
    int u, v; long long l, r; while(m--) read(u, v), read(l, r), cnt += l, add_edge(0, v, l), add_edge(u, v, r - l), add_edge(u, n + 1, l);
    add_edge(t, s, INF);
    long long ret = maxflow(s, t);
    if(ret == -1) puts("please go home to sleep");
    else printf("%lld\n", ret);
    return 0;
}

Network Flow(III) - Mincost Maxflow

Network Flow(III) - Mincost Maxflow

网络流笔记:最小费用最大流。

Theory

经过每条边,单位流量需支付费用。

最大流量的前提下,费用最小。

满足两个最优化限制的话,经典的思路其实就只有两个了:

  1. 不断选取费用最小的路增广,直到不能增广为止。
  2. 找出一个可行解后,慢慢调整。

步步为营,只维护可行性和最优性其中之一。(后者其实我也不会)

当然说是经典,也就是说还有更现代的。像是松弛算法网络单纯形什么的我既不会,也不是本文探讨重点。不过由于放宽了最优化限制,想必一定很快,但是也很难理解,代码也长。

Classic ZKW Algorithm

考虑不断选取费用最小的路增广,直到不能增广为止。

那其实和普通最大流相比,区别就是一个标号的过程。之前是只有可以走就贪心标号(其实那时叫分层),现在必须走通往终点的最短路。

可以用 KM 修改顶标的方式在每一次增广后修改顶标。

不能直接用于负权图,且在流量不大,费用不小,增广路较长的图表现得不够优秀。因为在零权网络中每次只能增加一条边,重复标号反复尝试增广而次次不能增广。

而且 KM 难写。

Primal-Dual ZKW Algorithm

我们不用 KM 了,不就是用最短路标号嘛,像 Dinic 一样每次用 SPFA 求标号不就好了吗。

牺牲复杂度换取实现简单也是很划算的。

注意一条边如果被走过,那一定比没走过其他边更优。所以只要没断,边权可以视为 $ 0 $ .

LOJ 102 (4126, 772)

#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
template<class T> void read(T& x)
{
    char c = getchar(); T p = 1, n = 0;
    while(c < '0' || c > '9') {if(c == '-') p = -1; c = getchar();}
    while(c >= '0' && c <= '9'){n = n * 10 + c - '0'; c = getchar();}
    x = p * n;
}
template<class T,class U>void read(T&x,U&y){read(x),read(y);}
template<class T,class U,class V>void read(T&x,U&y,V&z){read(x),read(y),read(z);}
const int maxn = 400 + 5, maxm = 15000 + 5, INF = 0x3f3f3f3f;
struct E{int v, n, f, c, w; } e[maxm << 1];
int h[maxn], ec = 2;
inline void add_edge(int u,int v,int c,int w){e[ec]=(E){v,h[u],0,c,w};h[u]=ec++;e[ec]=(E){u,h[v],0,0,-w};h[v]=ec++;}
deque<int> Q;
bool inq[maxn]; int d[maxn];
int n, m, c, cost;
bool bfs()
{
    memset(inq, 0, sizeof inq); memset(d, INF, sizeof d);
    d[n] = 0; Q.push_back(n); inq[n] = true;
    while(!Q.empty())
    {
        int u = Q.front(); Q.pop_front();
        for(int i = h[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(e[i ^ 1].f < e[i ^ 1].c && d[v] > d[u] - e[i].w)
        {
            d[v] = d[u] - e[i].w;
            if(!inq[v])
            {
                if(!Q.empty() && d[v] < d[Q.front()]) Q.push_front(v); else Q.push_back(v);
                inq[v] = true;
            }
        }
        inq[u] = false;
    }
    for(int i = 2; i < ec; ++i) e[i].w += d[e[i].v] - d[e[i ^ 1].v];
    c += d[1];
    return d[1] != INF;
}
bool vis[maxn]; int cur[maxn];
int dfs(int u = 1, int a = INF)
{
    if(u == n || !a) return cost += a * c, a;
    int flow = 0, f; vis[u] = true;
    for(int&i = cur[u], v = e[i].v; i; i = e[i].n, v = e[i].v) if(e[i].f < e[i].c && !e[i].w && !vis[v] && (f = dfs(v, min(a, e[i].c - e[i].f))))
    {
        e[i].f += f, e[i ^ 1].f -= f, flow += f, a -= f;
        if(!a) return flow;
    }
    return flow;
}
int flow()
{
    int ret = 0, f;
    while(bfs()) 
    {
        memcpy(cur, h, sizeof h); 
        do memset(vis, 0, sizeof vis), ret += (f = dfs()); while(f);
    }
    return ret;
}
int main()
{
    read(n, m);
    int s, t, c, w; while(m--) read(s, t), read(c, w), add_edge(s, t, c, w);
    int f = flow();
    printf("%d %d\n", f, cost);
    return 0;
}