In mathematics you don't understand things, you just get used to them.

标签 flows 下的文章

「codeforces - 1592F2」Alice and Recoloring 2:妙妙题,显然只会操作 $(1, 1)$ 和 $(n, m)$,分别记作操作 1 和操作 2。我们希望单点操作,所以考虑差分(trick),只是这个题的差分非常高妙。如果你直接对前缀做二维差分会发现操作 1 要修改四个点,操作 2 修改一个点,而操作 1 花费 $1$,2 花费 $2$,所以每个点可能被操作最多两次(考虑一种情况,即 $(1, 1)$,$(1, 2)$,$(2, 1)$ 是黑点,$(2, 2)$ 是白点)。xf 教我了一种高庙的差分,具体是 $c_{i, j} = a_{i,j} \oplus a_{i+1, j} \oplus a_{i, j+1} \oplus a_{i+1, j+1}$,这样差分操作 2 就修改四个点,操作 1 修改一个点,这样一个点就最多修改一次了,建出二分图即可。

最后的做法是,假设我全部用操作 1,然后反悔看最多能用多少次操作 2(因为会用在 $(i, j)$ 上用操作 2 的情况是 $(i, j)$,$(n, j)$,$(i, m)$ 都是黑点)。

最后 $(n, m)$ 是需要特判的。

各种意义上我不会的题,膜拜 xf。

char s[600];
int n, m, a[600][600];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> m;
    for (int i=1; i<=n; ++i) {
        cin >> s;
        for (int j=1; j<=m; ++j) {
            a[i][j] = s[j-1] == 'B';
        }
    }
    for (int i=1; i<=n; ++i) {
        for (int j=1; j<=m; ++j) {
            a[i][j] ^= a[i+1][j]^a[i][j+1]^a[i+1][j+1];
        }
    }
    mf_graph<int> g(n+m+2);
    const int src = n+m+1, ter = n+m+2;
    for (int i=1; i<n; ++i) {
        for (int j=1; j<m; ++j) {
            if (a[i][j] && a[n][j] && a[i][m]) {
                g.add_edge(i, j+n, 1);
            }
        }
    }
    for (int i=1; i<=n; ++i) g.add_edge(src, i, 1);
    for (int i=1; i<=m; ++i) g.add_edge(i+n, ter, 1);
    int ret = -g.flow(src, ter);
    a[n][m] ^= (-ret)&1;
    for (int i=1; i<=n; ++i) {
        for (int j=1; j<=m; ++j) ret += a[i][j];
    }
    cout << ret << "\n";
}

「codeforces - 1146G」Zoning Restrictions:还行的题。想到把每个点拆成 $h+1$ 个点,如果你定义状态 $(i, j)$ 表示第 $i$ 个点的高度为 $j$ 会发现并不优秀,改成高度大于等于 $j$ 会好一点。关于这题怎么想到 min-cut 的,可以这样考虑(which is also a common trick):全局最理想的答案是 $h^2 \times n$,然而不一定合法,我们以最小的代价去把这个答案调整到合法,得到的一定是最优的答案,因此是 min-cut。然而这道题我们不会这样去想,这只是一些题的思路。对于这个题我们的想法是把所有的贡献取负,这样就成了最小值()

这里涉及另一个思维上的 trick:最小割的源汇有什么意义?划分出的两个点集有什么意义?在这一题中我们不妨将点集 $S$ 中的事件称为「成立」,$T$ 中的事件称为「不成立」,这样就赋予了最小割意义。

这里给出构造网络的方案:$\lang(i, j), (i, j+1), -j^2\rang$,$\lang (j, 0), x_i+1, \infty \rang, \forall j \in [l_i, r_i]$,$\lang i, t, c_i \rang$。

考察意义:割掉第一类边的意义即令房子 $i$ 的高度为 $j$,这对答案的贡献为 $j^2$,我们对答案取了负,所以贡献为 $-j^2$,如果第二类边有流量则意味着付罚金,因为一个区间只需要付一次罚金,所以我们不能直接割掉第二类边,而是通过第二类边使得割掉第三类边有意义(断掉连通性)。

当然流量不可能是负的,所以选择给 $-j^2$ 加上 $h^2$,因为 $n$ 幢房子代表的每一条链必然割且仅割一条一类边,所以最后给答案加上 $h^2 \times n$ 即可。

const int inf = 0x3f3f3f3f;
int n, h, m, id[60][60], qwq;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> h >> m;
    for (int i=1; i<=n; ++i) {
        for (int j=0; j<=h+1; ++j) id[i][j] = ++qwq;
    }
    mf_graph<int> g(qwq+m+2);
    const int src = qwq+m+1, ter = qwq+m+2;
    for (int i=1; i<=n; ++i) {
        g.add_edge(src, id[i][0], inf);
    }
    for (int i=1; i<=n; ++i) {
        for (int j=0; j<=h; ++j) g.add_edge(id[i][j], id[i][j+1], h*h-j*j);
    }
    for (int i=1,l,r,x,c; i<=m; ++i) {
        cin >> l >> r >> x >> c;
        g.add_edge(i+qwq, ter, c);
        for (int j=l; j<=r; ++j) {
            g.add_edge(id[j][x+1], i+qwq, inf);
        }
    }
    cout << h*h*n-g.flow(src, ter) << "\n";
}

「codeforces - 1061E」Politics:第一步是想到把 tree 1 放左部 tree 2 放右部。然后进入这个题的 key step:将对于子树的限制(被钦定的结点个数)转化为对一个结点的限制。这里大部分的做法是:对于一个有限制的结点 $x$,将 $k_x$ 减去 $\displaystyle \sum_{y \in \text{subtree}(x), y \neq x} k_y$,从而我们的工作就变成了在「所有 $x$ 的子树中的有限制的结点的上方中钦定一些结点」。于是给出这题的建图(费用流):$\displaystyle \lang s, x \in\textbf V_1,k_x-\sum_{y \in \text{subtree}(x), y \neq x} k_y, 0\rang$,$\displaystyle \lang x \in\textbf V_2, t,k_x-\sum_{y \in \text{subtree}(x), y \neq x} k_y, 0\rang$,$\lang up_i, up'_i, 1, a_i\rang$,其中 v_1 v_2 是两棵树的点集,up_i 是编号 $i$ 在 tree 1 中对应的结点的最近有限制祖先,up'_i 同理。

理解一下,一、二类边都对应每个结点的限制,第三类边就是反过来考虑(我们之前是对祖先 $x$ 考虑,要在子树中所有有限制的结点的上面钦定结点),现在我们对一个点 $x$ 考虑它对最近有限制祖先的贡献。如果钦定了编号 $i$,则会对答案造成 $a_i$ 的贡献,占据 up_i 和 up'_i 的空余可钦定数。

mcf_graph<int, int> g;
int src, ter, qwq, qaq;
int n, rtx, rty, q1, q2, a[600], bstx[600], bsty[600], upx[600], upy[600], parx[600], pary[600];
bsi<int> adjx[600], adjy[600];
int dfsx(int x, int pa, int t) {
    parx[x] = pa;
    if (bstx[x]) {
        t = x;
    }
    upx[x] = t;
    int sum = 0;
    for (auto y : adjx[x]) {
        if (y != pa) {
            sum += dfsx(y, x, t);
        }
    }
    if (bstx[x]) {
        if (bstx[x] < sum) fucked_up();
        qwq += bstx[x]-sum;
        g.add_edge(src, x, bstx[x]-sum, 0);
        return bstx[x];
    }
    return sum;
}
int dfsy(int x, int pa, int t) {
    pary[x] = pa;
    if (bsty[x]) {
        t = x;
    }
    upy[x] = t;
    int sum = 0;
    for (auto y : adjy[x]) {
        if (y != pa) {
            sum += dfsy(y, x, t);
        }
    }
    if (bsty[x]) {
        if (bsty[x] < sum) fucked_up();
        qaq += bsty[x]-sum;
        g.add_edge(x+n, ter, bsty[x]-sum, 0);
        return bsty[x];
    }
    return sum;
}
int getx(int x, int pa) {
    int res = 0;
    for (auto y : adjx[x]) {
        if (y != pa) res += getx(y, x);
    }
    if (bstx[x]) {
        return bstx[x];
    }
    return res;
}
int gety(int x, int pa) {
    int res = 0;
    for (auto y : adjy[x]) {
        if (y != pa) res += gety(y, x);
    }
    if (bsty[x]) {
        return bsty[x];
    }
    return res;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> rtx >> rty;
    for (int i=1; i<=n; ++i) {
        cin >> a[i];
    }
    for (int i=1,x,y; i<n; ++i) {
        cin >> x >> y;
        adjx[x] += y, adjx[y] += x;
    }
    for (int i=1,x,y; i<n; ++i) {
        cin >> x >> y;
        adjy[x] += y, adjy[y] += x;
    }
    cin >> q1;
    for (int i=0,x; i<q1; ++i) {
        cin >> x >> bstx[x];
    }
    cin >> q2;
    for (int i=0,x; i<q2; ++i) {
        cin >> x >> bsty[x];
    }
    g = mcf_graph<int, int>(n*2+2);
    src = n*2+1, ter = n*2+2;
    dfsx(rtx, 0, 0), dfsy(rty, 0, 0);
    if (qwq != qaq) fucked_up();
    for (int i=1; i<=n; ++i) {
        if (upx[i] && upy[i]) {
            g.add_edge(upx[i], upy[i]+n, 1, -a[i]);
        }
    }
    auto [X, Y] = g.flow(src, ter);
    if (X != qwq) fucked_up();
    cout << -Y << "\n";
}

消去网络中负权边的方法。

首先不能给边势能函数,因为最短路的路径不一定一致。于是在点上做文章,给每个点一个势能函数 $h(x)$,满足 $\forall (x,y)\in E,s.t.h(x)-h(y)+w(x,y)\geqslant0$,这样跑出来的最短路多的权就是 $h(s)-h(t)$。

至于构造方法,每次增广完以后,如果 $(x,y)\in E_r$,则 $y$ 一定是 $x$ 的 successor(不然就不在增广路上),也就是说 $d_x+w(x,y)+h(x)-h(y)=d_y$,其中 $d$ 是 $s$ 到该点的最短路,可得 $(d_y+h(y))-(d_x+h(x))=w(x,y)$。

那么令 $h(x):=h(x)+d(x)$ 即可。

因为不想写指针所以用了 std::vector 管理边的内存……

TS; DR

template<typename type1, typename type2,
    const type1 inf1 = std::numeric_limits<type1>::max(),
    const type2 inf2 = std::numeric_limits<type2>::max()>
struct mcf_graph {
    const int n;
    struct edge {
        int to, nxt;
        type1 c; type2 f;
        edge(int to, int nxt, type1 c, type2 f) : to(to), nxt(nxt), c(c), f(f) {}
    };
    int *head, *pre, *id, *vis;
    std::vector<edge> e;
    type2 *h, *dis;
    mcf_graph(const int n) : n(n),
        head(new int[n+1]), pre(new int[n+1]), id(new int[n+1]), vis(new int[n+1]),
        h(new type2[n+1]), dis(new type2[n+1]) { std::memset(head, -1, (n+1)<<2); }
    void add_edge(const int x, const int y, type1 c, type2 f) {
        assert(1 <= x && x <= n && 1 <= y && y <= n);
        e.emplace_back(y, head[x], c, f),head[x] = int(e.size())-1;
        e.emplace_back(x, head[y], 0, -f),head[y] = int(e.size())-1;
    }
    void initialize_potentials(const int s) {
        std::queue<int> q;
        std::fill(h+1, h+n+1, inf2);
        std::memset(vis+1, 0, n<<2);
        for(q.push(s), vis[s] = 1, h[s] = 0; q.size(); vis[q.front()] = 0, q.pop()) {
            for(int now = q.front(), i = head[now], y; ~i; i = e[i].nxt) {
                if(e[i].c && (h[y = e[i].to] == inf2 || h[y] > h[now]+e[i].f)) {
                    h[y] = h[now]+e[i].f;
                    if(!vis[y]) q.push(y),vis[y] = 1;
                }
            }
        }
    }
    bool augment(const int s, const int t) {
        std::priority_queue<std::pair<type2, int>, std::vector<std::pair<type2, int>>, std::greater<std::pair<type2, int>>> q;
        std::fill(dis+1, dis+n+1, inf2);
        std::memset(vis+1, 0, n<<2);
        for(q.emplace(dis[s] = 0, s); q.size();) {
            int now = q.top().second;
            q.pop();
            if(vis[now]) continue;
            vis[now] = 1;
            for(int i = head[now], y; ~i; i = e[i].nxt) {
                if(e[i].c && (dis[y = e[i].to] == inf2 || dis[y] > dis[now]+e[i].f+h[now]-h[y])) {
                    dis[y] = dis[now]+e[i].f+h[now]-h[y];
                    pre[y] = now,id[y] = i;
                    if(!vis[y]) q.emplace(dis[y], y);
                }
            }
        }
        return dis[t] < inf2;
    }
    std::pair<type1, type2> get(const int s, const int t) {
        assert(1 <= s && s <= n && 1 <= t && t <= n);
        type1 res1 = 0; type2 res2 = 0;
        initialize_potentials(s);
        while(augment(s, t)) {
            type1 cur = inf1;
            for(int i = 1; i <= n; ++i) h[i] += dis[i];
            for(int i = t; i != s; i = pre[i]) cmin(cur, e[id[i]].c);
            for(int i = t; i != s; i = pre[i]) e[id[i]].c -= cur,e[id[i]^1].c += cur;
            res1 += cur,res2 += cur*h[t];
        }
        return std::make_pair(res1, res2);
    }
};

现在来看一年前的傻逼代码太丑了,换了个好看的:

template <class Cap, class Cost>
struct mcf_graph {
  using pcc = pair<Cap, Cost>;
  struct edge {
    int v, rv;
    Cap c;
    Cost w;
  };
  const int _n;
  vi<vi<edge>> e;
  mcf_graph() : mcf_graph(0) {}
  explicit mcf_graph(int n) : _n(n), e(n + 1) {}
  void add_edge(int from, int to, const Cap& c, const Cost& w) {
    assert(1 <= from && from <= _n);
    assert(1 <= to && to <= _n);
    e[from].pb({to, int(e[to].size()) + (from == to), c, w});
    e[to].pb({from, int(e[from].size()) - 1, 0, -w});
  }
  pcc flow(int s, int t) {
    assert(1 <= s && s <= _n);
    assert(1 <= t && t <= _n);
    return flow(s, t, numeric_limits<Cap>::max(), numeric_limits<Cost>::max());
  }
  pcc flow(int s, int t, const Cap& flow_limit, const Cost& cost_limit) {
    vi<Cost> h(_n + 1, cost_limit), dst(_n + 1, cost_limit);
    vi<bool> vis(_n + 1);
    vi<int> pre(_n + 1), id(_n + 1);
    auto dual = [&](int s) {
      queue<int> que;
      for (que.push(s), h[s] = 0, vis[s] = 1; !que.empty();
           vis[que.front()] = 0, que.pop())
        for (int x = que.front(), i = 0, y; i < int(e[x].size()); ++i)
          if (e[x][i].c && h[y = e[x][i].v] > h[x] + e[x][i].w) {
            h[y] = h[x] + e[x][i].w;
            if (!vis[y]) que.push(y), vis[y] = 1;
          }
    };
    auto aug = [&](int s, int t) {
      using pci = pair<Cost, int>;
      priority_queue<pci, vi<pci>, greater<pci>> que;
      dst.assign(_n + 1, cost_limit);
      vis.assign(_n + 1, 0);
      for (que.emplace(dst[s] = 0, s); !que.empty();) {
        int x = que.top().second;
        que.pop();
        if (!vis[x]) {
          vis[x] = 1;
          for (int i = 0, y; i < int(e[x].size()); ++i) {
            y = e[x][i].v;
            if (e[x][i].c && dst[y] > dst[x] + e[x][i].w - h[y] + h[x]) {
              dst[y] = dst[x] + e[x][i].w + h[x] - h[y];
              pre[y] = x, id[y] = i;
              if (!vis[y]) que.emplace(dst[y], y);
            }
          }
        }
      }
      return dst[t] < cost_limit;
    };
    Cap flow = 0;
    Cost cost = 0;
    dual(s);
    while (aug(s, t)) {
      Cap cur = flow_limit;
      for (int i = 1; i <= _n; ++i) h[i] += dst[i];
      for (int u = t; u != s; u = pre[u]) cmin(cur, e[pre[u]][id[u]].c);
      for (int u = t; u != s; u = pre[u])
        e[pre[u]][id[u]].c -= cur, e[u][e[pre[u]][id[u]].rv].c += cur;
      flow += cur, cost += cur * h[t];
    }
    return pcc(flow, cost);
  }
};

link。

也许题不错,反正有点降智…

先给结论,在

$$ V_N=V \\ E_N=E \\ c(x,y)=w(x,y) $$

的流网络中:

  • 可行边:在增广完的 induced subgraph 中,不存在 $u$ 到 $v$ 的路径
  • 必要边:在增广完的 induced subgraph 中,可以从 $S$ 到 $u$ 且可以从 $v$ 到 $T$。

先看可行边。不存在 $(u,v)$ 有两个条件,一个是 $c_f(u,v)=0$,另一个是与之并联(特指以 $u$ 为起点,$v$ 为终点的)的线路中存在 $c_f(u',v')=0$。第一个的理解是,如果它没满流,则与之串联的 arcs 中存在比它的容量更小的边,根据最小割串联割最小的原则成立;第二个就是,如果你不把并联的砍了,你的划分压根不合法,何谈可行与否。

那么关于可行边的判断,把图缩点即可。

再看必要边。这个类比可行边即可,不赘述。

#include<bits/stdc++.h>
using namespace std;
int n,m,S,T,co[4100],dfsnt,colnt,inst[4100],sta[4100],top,dfn[4100],low[4100],vi[4100],rec[60100];
vector<pair<int,int>> arc;
template<typename T> struct network {
    const int n;
    struct edge {
        int to,r; T w;
//        friend bool operator<(const edge& one,const edge& ano) { return one.to<ano.to || (one.to==ano.to && one.r<ano.r); }
    };
    vector<vector<edge>> e;
    vector<int> lev,iter;
    network(int n):n(n),e(n+1),lev(n+1),iter(n+1) {}
    void link(const int x,const int y,T w,int ID) {
        assert(1<=x && x<=n && 1<=y && y<=n);
        rec[ID]=int(e[x].size());
        e[x].push_back((edge){y,int(e[y].size())+(x==y),w});
        e[y].push_back((edge){x,int(e[x].size())-1,0});
    }
    bool BFS(const int s,const int t) {
        queue<int> q; lev.assign(n+1,0);
        for(q.push(s),lev[s]=1; q.size(); q.pop()) {
            for(int now=q.front(),i=iter[now]=0,y; i<int(e[now].size()); ++i) {
                if(now==t)    return 1;
                if(!lev[y=e[now][i].to] && e[now][i].w)    lev[y]=lev[now]+1,q.push(y);
            }
        }
        return lev[t];
    }
    T DFS(const int now,T f,const int t) {
        if(now==t)    return f;
        T res=0,tt;
        for(int& i=iter[now],y; i<int(e[now].size()); ++i) {
            if(lev[y=e[now][i].to]==lev[now]+1 && e[now][i].w && (tt=DFS(y,min(f,e[now][i].w),t))) {
                e[now][i].w-=tt; e[y][e[now][i].r].w+=tt; res+=tt; f-=tt;
                if(!f)    break;
            }
        }
        if(!res)    lev[now]=0;
        return res;
    }
    T get(const int s,const int t) {
        T res=0;
        while(BFS(s,t))    res+=DFS(s,numeric_limits<T>::max(),t);
        return res;
    }
};
template<typename T=int> T rd() {
    T x=0; char IO=getchar(); bool f=0;
    while(IO<'0' || IO>'9')    f|=IO=='-',IO=getchar();
    while(IO>='0' && IO<='9')    x=x*10+(IO&15),IO=getchar();
    return f?-x:x;
}
void DFS(const int now,const network<int>& G) {
    dfn[now]=low[now]=++dfsnt;
    inst[sta[++top]=now]=1;
    for(int i=0,y; i<int(G.e[now].size()); ++i) {
        if(!G.e[now][i].w)    continue;
        if(!dfn[y=G.e[now][i].to])    DFS(y,G),low[now]=min(low[now],low[y]);
        else if(inst[y])    low[now]=min(low[now],dfn[y]);
    }
    if(dfn[now]==low[now]) {
        ++colnt;
        while(sta[top]!=now)    co[sta[top]]=colnt,inst[sta[top]]=0,top--;
        top--; co[now]=colnt; inst[now]=0;
    }
}
void DFS_network(const int now,const network<int>& G,const int c) {
    vi[now]=c;
    for(int i=0,y; i<int(G.e[now].size()); ++i) {
        if(!vi[y=G.e[now][i].to] && (c-1?G.e[y][G.e[now][i].r].w:G.e[now][i].w))    DFS_network(y,G,c);
    }
}
signed main() {
    freopen("mincut.in","r",stdin);
    freopen("mincut.out","w",stdout);
    n=rd(); m=rd(); S=rd(); T=rd();
    network<int> G(n);
    for(int i=0,x,y; i<m; ++i)    x=rd(),y=rd(),G.link(x,y,rd(),i),arc.emplace_back(x,y);
    for(int i=(G.get(S,T),1); i<=n; ++i)    if(!dfn[i])    DFS(i,G);
    DFS_network(S,G,1); DFS_network(T,G,2);
//    for(int i=1; i<=n; ++i)    printf(" --- %d ",vi[i]); puts("\n");
//    for(int i=1; i<=n; ++i)    printf(" --- %d ",co[i]); puts("");
//    for(int i=1; i<=n; ++i)    sort(G.e[i].begin(),G.e[i].end());
//    for(int now=1; now<=n; ++now) {
//        printf(" --- current node = %d:",now);
//        for(int i=0; i<int(G.e[now].size()); ++i)    printf(" %d",G.e[now][i].to);
//        puts("");
//    }
    for(int i=0; i<m; ++i) {
//        printf(" (%d %d)\n",arc[i].first,lower_bound(G.e[arc[i].first].begin(),G.e[arc[i].first].end(),(network<int>::edge){arc[i].second,0,0})->to);
//        if(lower_bound(G.e[arc[i].first].begin(),G.e[arc[i].first].end(),(network<int>::edge){arc[i].second,0,0})->w)    puts("0 0");
//        printf(" (%d %d)[%d %d]\n",arc[i].first,G.e[arc[i].first][rec[i]].to,co[arc[i].first],co[G.e[arc[i].first][rec[i]].to]);
        if(G.e[arc[i].first][rec[i]].w)    puts("0 0");
        else    printf("%d %d\n",co[arc[i].first]!=co[arc[i].second],vi[arc[i].first]==1 && vi[arc[i].second]==2);
    }
    return 0;
}

1. Introduction

   Define a closure of a directed graph $G=(V,E)$ as an induced set of vertexes of $G$, where the out-tended edges in $E$ start at some vertex in the closure of $G$ and end at some vertex in the closure, too. More formally, closure is defined as a set of vertexes $V'\subseteq V$, such that $\forall u\in V',s.t.\forall\left<u,v\right>\in E,v\in V'$.

   For the Maximum Weight Closure of a Graph, we define it as, with starting out endowing vertexes in $V'$ costs called $w_u$, the maximum value of $\sum\limits_{u\in V'}w_u$.

2. Construction of Algorithm

   Add a source $s$ and a sink $t$ to the original graph, endow the origin edges in $E$ with capacities that $+\infty$, and connect $s$ with points with positive costs with the points' costs as capacities on them while $t$ connects to those with negative costs with the minus points' costs as capacities on them, where $+\infty$ is defined as any number greater or equal to $\sum |w_i|$.

   More formally, as explained & illustrated:

$$ V_N=V\cup\{s,t\} \\ E_N=E\cup\left\{\left<s,v\right>\mid v\in V,w_v\geqslant0\right\}\cup\left\{\left<v,t\right>\mid v\in V,w_v<0\right\} \\ \begin{cases} c(u,v)=+\infty,\left<u,v\right>\in E \\ \displaystyle c(s,v)=w_v,w_v\geqslant0 \\ \displaystyle c(v,t)=-w_v,w_v<0 \end{cases} $$

   Which make it easy to understand the answer would be $\text{min-cut}(G_N=\{V_N,E_N\})$.


[1] Referenced to 最小割模型在信息学竞赛中的应用 by 胡伯涛 Amber.

看向这样一份 Dinic 的实现:

#include<bits/stdc++.h>
using namespace std;
template<typename T> struct network {
    const int n;
    struct Arc {
        int to; size_t rev; T w;
    };
    vector<vector<Arc>> e;
    vector<int> lev,iter;
    network(int n):n(n),lev(n),iter(n),e(n) {}
    void add(int one,int ano,T cap) {
        assert(1<=one && one<=n && 1<=ano && ano<=n);
        e[one-1].push_back((Arc){ano-1,e[ano-1].size()+(one==ano),cap});
        e[ano-1].push_back((Arc){one-1,e[one-1].size()-1,0});
    }
    bool bfs(int s,int t) {
        queue<int,list<int>> q;
        lev.assign(n,0);
        for(q.push(s),lev[s]=1; q.size(); q.pop()) {
            for(int now=q.front(),i=iter[now]=0,y; i<int(e[now].size()); ++i) {
                if(now==t)    return 1;
                if(!lev[y=e[now][i].to] && e[now][i].w)    q.push(y),lev[y]=lev[now]+1;
            } 
        }
        return lev[t];
    }
    T dfs(int now,T f,int t) {
        if(now==t)    return f;
        T res=0,tt;
        for(int& i=iter[now],y; i<int(e[now].size()); ++i) {
            if(!f)    break;
            if(lev[y=e[now][i].to]==lev[now]+1 && e[now][i].w && (tt=dfs(y,min(f,e[now][i].w),t))) {
                e[now][i].w-=tt; e[y][e[now][i].rev].w+=tt; f-=tt; res+=tt;
            }
        }
        if(!res)    lev[now]=0;
        return res;
    }
    T get(int s,int t) {
        assert(1<=s && s<=n && 1<=t && t<=n);
        T res=0,f;
        while(bfs(s-1,t-1)) {
            if((f=dfs(s-1,numeric_limits<T>::max(),t-1)))    res+=f;
            else    break;
        }
        return res;
    }
};
signed main() {
    int n,m,s,t;
    scanf("%d %d %d %d",&n,&m,&s,&t);
    network<long long> G(n);
    for(int i=0,x,y,z; i<m; ++i) {
        scanf("%d %d %d",&x,&y,&z);
        G.add(x,y,z);
    }
    printf("%lld\n",G.get(s,t));
    return 0;
};

它在洛谷的最大流模板中取得了 sum runtime 440ms 的傻逼成绩,卡了我至少 4 题的常。

让我们来看看为什么这份实现跑得这么慢。

首先我使用了 std::size_t 来储存反向边的编号,然而实际上 std::size_tstd::vector<Type>::size_type) 在 64-bits 机器中是 long long unsigned int 的别名,也即,这个跑得比 int 慢多了。

其次是 std::queue<int, list<int>>,虽然 -Wallace- 在其 STL 博客中表示这将比 std::queue<int, deque<int>> 更快,但事实上至少在这里它跑得很慢……

最后是

for(int& i=iter[now],y; i<int(e[now].size()); ++i) {
    if(!f)    break;
    if(lev[y=e[now][i].to]==lev[now]+1 && e[now][i].w && (tt=dfs(y,min(f,e[now][i].w),t))) {
        e[now][i].w-=tt; e[y][e[now][i].rev].w+=tt; f-=tt; res+=tt;
    }
}

在经过实验对比(指,摸到 loj 势力上看了下为啥神户的板子跑这么快)后,我们惊讶的发现,下面代码所呈现的跑进了 50ms

for(int& i=iter[now],y; i<int(e[now].size()); ++i) {
    if(lev[y=e[now][i].to]==lev[now]+1 && e[now][i].w && (tt=dfs(y,min(f,e[now][i].w),t))) {
        e[now][i].w-=tt; e[y][e[now][i].rev].w+=tt; f-=tt; res+=tt;
        if(!f)    break;
    }
}

起初我以为是我的 $f$ 可能变成负数,但理论和事实都给了我两耳巴子。然后我怀疑是 std::vector<Type>::size() 的问题,但依然没有区别。

最后的怀疑是 ++i 的效率问题

for(int& i=iter[now],y; i<int(e[now].size()); ++i) {
    if(lev[y=e[now][i].to]==lev[now]+1 && e[now][i].w && (tt=dfs(y,min(f,e[now][i].w),t))) {
        e[now][i].w-=tt; e[y][e[now][i].rev].w+=tt; f-=tt; res+=tt;
        if(!f)    {++i; break;}
    }
}

飘成了 400ms,破案了?

不,再看

for(int i=iter[now],y; i<int(e[now].size()); ++i) {
    iter[now]=i;
    if(lev[y=e[now][i].to]==lev[now]+1 && e[now][i].w && (tt=dfs(y,min(f,e[now][i].w),t))) {
        e[now][i].w-=tt; e[y][e[now][i].rev].w+=tt; f-=tt; res+=tt;
        if(!f)    {++i; break;}
    }
}

又跑进了 50ms,现在明了了,iterstd::vector<int>)的寻址太慢,并且剪枝剪掉的情况确实比较多,所以效率很低。