洛谷 P6938 [ICPC2017 WF]Son of Pipe Stream 题解

· · 题解

一、题目:

洛谷原题

codeforces原题

二、思路:

这道题整整让我调了一个下午!一直被精度卡,后来把所有的精度全部取消就 A 了!

现在让我们来系统梳理一下这道题的思路。

首先可以看出,题目中的参数 v 纯粹是迷惑人的。我们完全可以将 v\times f 看成一个新流。最终求出来答案再除以 v^a 即可。

我们试想这样一种情况:新建一个超级源点 \mathfrak{s},从 \mathfrak{s} 分别向 1、2 号点连容量是无穷大的边,设从 \mathfrak{s} 向 3 号点跑最大流得到的答案是 Z。那么一定有 F+W=Z

最终的答案设成一个函数 f(F)=F^a(Z-F)^{1-a},对该函数求导,得

\begin{aligned} f'(F)&=aF^{a-1}(Z-F)^{1-a}-F^a(1-a)(Z-F)^{-a}\\ &=F^a(Z-F)^{-a}\left(\dfrac{a(Z-F)}{F}-1+a\right)\\ &=F^a(Z-F)^{-a}\dfrac{aZ-F}{F} \end{aligned}

则当 F<aZ 时,f'(F)>0;当 F>aZ 时,f'(F)<0。所以当 F=aZ 时,f(F) 有极大值。

但是接下来有两个问题亟待我们去解决。

第一个问题是我们可能无法让 F 达到 aZ。设从 1 号点向 3 号点跑最大流的答案是 F_{\max},从 2 号点向 3 号点跑最大流的答案是 W_{\max}。那么,F 的范围(或者可以理解为定义域)为 [Z-W_{\max},F_{\max}]。所以我们只需要在 F 的定义域找见距离 aZ 最近的值即可。设该值为 F^*W^*=Z-F^*

第二个问题是,有可能 F^* 所对应的流和 W^* 所对应的流叠加在一起时,会造成同一根水管会有不同方向的两条流。但是,我们可以构造出一种满足题意的流。

  1. \mathfrak{s} 向 1 号点连容量为 F^* 的边,向 2 号点连容量是 W^* 的边。再从 \mathfrak{s} 向 3 号点跑最大流。
  2. 新建一张图 G,新图边的方向为旧图中最大流的流向,容量为旧图中最大流的流量。
  3. 从新图的超级源点 \mathfrak{s} 向 1 号点连容量为 F^* 的边,再从 \mathfrak{s} 向 3 号点跑最大流。此时每条边的流量就是 Flubber 的最终答案,每条边的容量减流量就是 water 的最终答案。

显然,由于新图的每条边的流量守恒,容量也守恒,所以 water 的流量也是守恒的。而且 Flubber 的流量一定是 F^*,water 的流量一定是 W^*

三、代码:

#include <iostream> // 不要设任何的eps!
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;
#define FILEIN(s) freopen(s, "r", stdin)
#define FILEOUT(s) freopen(s, "w", stdout)
#define mem(s, v) memset(s, v, sizeof s)

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int MAXN = 205, MAXM = MAXN * MAXN;
const double INF = 1e9;

int n, m, head[MAXN], tot = 1, S, T;
int q[MAXN], cur[MAXN], d[MAXN];
int dir[MAXM];
double Fmax, Wmax, Z, F$, W$, ans, v, a;

struct Edge {
    int y, next;
    double w;
    Edge() {}
    Edge(int _y, int _next, double _w) : y(_y), next(_next), w(_w) {}
}e[MAXM];

inline void add(int x, int y, double w) {
    e[++ tot] = Edge(y, head[x], w);
    head[x] = tot;
}

inline void connect(int x, int y, double w) {
    add(x, y, w); add(y, x, w);
}

bool bfs(void) {
    int hh = 0, tt = -1;
    q[++ tt] = S; cur[S] = head[S];
    mem(d, -1); d[S] = 0;
    while (hh <= tt) {
        int x = q[hh ++];
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].y;
            if (e[i].w > 0 && d[y] == -1) {
                d[y] = d[x] + 1;
                cur[y] = head[y];
                if (y == T) return true;
                q[++ tt] = y;
            }
        }
    }
    return false;
}

double find(int x, double limit) {
    if (x == T) return limit;
    double flow = 0;
    for (int i = cur[x]; i && flow < limit; i = e[i].next) {
        cur[x] = i;
        int y = e[i].y;
        if (e[i].w > 0 && d[y] == d[x] + 1) {
            double t = find(y, min(e[i].w, limit - flow));
            if (t == 0) d[y] = -1;
            e[i].w -= t; e[i ^ 1].w += t; flow += t;
        }
    }
    return flow;
}

double dinic(void) {
    double r = 0, flow;
    while (bfs()) {
        if ((flow = find(S, INF)) > 0) r += flow;
    }
    return r;
}

inline void restore(void) {
    for (int i = 2; i <= tot; i += 2) {
        double mid = (e[i].w + e[i ^ 1].w) / 2.0;
        e[i].w = e[i ^ 1].w = mid;
    }
}

int main() {
    n = read(); m = read(); scanf("%lf%lf", &v, &a);
    for (int i = 1; i <= m; ++ i) {
        int x = read(), y = read();
        double w; scanf("%lf", &w);
        connect(x, y, w);
    }
    S = 1; T = 3;
    Fmax = dinic(); restore();

    S = 2;
    Wmax = dinic(); restore();

    S = n + 1; connect(S, 1, INF); connect(S, 2, INF);
    Z = dinic(); restore();

    if (Z - Wmax > a * Z) F$ = Z - Wmax;
    else if (Fmax < a * Z) F$ = Fmax;
    else F$ = a * Z;
    W$ = Z - F$;

    ans = pow(F$, a) * pow(W$, 1 - a);
    ans /= pow(v, a);

    for (int i = head[n + 1]; i; i = e[i].next) {
        int y = e[i].y;
        if (y == 1) e[i].w = F$, e[i ^ 1].w = 0;
        else e[i].w = W$, e[i ^ 1].w = 0;
    }
    dinic();

    for (int i = 2; i <= tot; i += 2) {
        double mid = (e[i].w + e[i ^ 1].w) / 2;
        if (e[i ^ 1].w > mid) { dir[i] = 1; e[i].w = e[i ^ 1].w - mid; e[i ^ 1].w = 0; }
        else { dir[i] = -1; e[i ^ 1].w = e[i].w - mid; e[i].w = 0; }
    }
    for (int i = head[n + 1]; i; i = e[i].next) {
        int y = e[i].y;
        if (y == 1) { e[i].w = F$; e[i ^ 1].w = 0; }
        else e[i].w = e[i ^ 1].w = 0;
    }

    S = n + 1;
    dinic();
    tot -= 4;
    for (int i = 2; i <= tot; i += 2) {
        if (dir[i] == 1)
            printf("%.8lf %.8lf\n", e[i ^ 1].w / v, e[i].w);
        else
            printf("-%.8lf -%.8lf\n", e[i].w / v, e[i ^ 1].w);

    }
    printf("%.8lf\n", ans);
    return 0;
}