洛谷 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;
}
posted @ 2021-07-05 10:23  蓝田日暖玉生烟  阅读(87)  评论(0编辑  收藏  举报