[BZOJ3130][Sdoi2013]费用流(结论+二分答案+最大流)

第一问模板最大流。
对于第二问,考虑如果已经确定了最大流方案,那么如何分配使得费用最大。
假设先随机分配,然后可以发现,如果两条边 e1,e2 e 1 , e 2 ,通过 e1 e 1 的流量大于通过 e2 e 2 的流量,那么如果将 e1 e 1 的费用增加 k k e2的费用减小 k k k是正数),那么费用一定会更大。
按照这个贪心,得出结论:费用最大的方案为流量(不是容量)最大的边分配到的费用为 P P ,其他的边分配到的费用为0
于是问题转化为:求一种最大流方案,使得流量最大的边的流量尽可能小。
看到是求最大值最小,因此二分答案(注意是在实数域上的二分),每次判定(设当前要判定的解为 mid m i d )时重新建图:
对于每一条边,如果原先的容量为 c c ,则此次建图时该边的容量为min(mid,c)
建图后跑最大流,如果等于第一问的答案,则最优解 mid ≤ m i d
第二问的答案就等于上面得出的最优解乘上 P P <script type="math/tex" id="MathJax-Element-2003">P</script>。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Edge(u) for (int e = adj[u], v; e; e = nxt[e])
using namespace std;
inline int read()
{
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const double eps = 1e-8;
const int N = 105, M = 2018;
int n, m, P, _u[M], _v[M], _cap[M], ecnt = 1, nxt[M], adj[N], go[M], lev[N],
len, que[M];
double cap[M], Ans;
void add_edge(int u, int v, double w)
{
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; cap[ecnt] = w;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; cap[ecnt] = 0;
}
bool bfs()
{
    int i;
    For (i, 1, n) lev[i] = -1;
    lev[que[len = 1] = 1] = 0;
    For (i, 1, len)
    {
        int u = que[i];
        Edge(u) if (cap[e] > eps && lev[v = go[e]] == -1)
        {
            lev[que[++len] = v] = lev[u] + 1;
            if (v == n) return 1;
        }
    }
    return 0;
}
double dinic(int u, double flow)
{
    if (u == n) return flow;
    double res = 0, delta = 0;
    Edge(u)
        if (cap[e] > eps && lev[u] < lev[v = go[e]])
        {
            delta = dinic(v, min(cap[e], flow - res));
            if (delta > eps)
            {
                cap[e] -= delta; cap[e ^ 1] += delta;
                res += delta;
                if (fabs(flow - res) <= eps) break;
            }
        }
    if (fabs(flow - res) > eps) lev[u] = -1;
    return res;
}
double solve()
{
    double ans = 0;
    while (bfs()) ans += dinic(1, 1e12);
    return ans;
}
bool check(double mid)
{
    int i; ecnt = 1;
    For (i, 1, n) adj[i] = 0;
    For (i, 1, m)
        add_edge(_u[i], _v[i], min(1.0 * _cap[i], mid));
    return fabs(Ans - solve()) <= eps;
}
int main()
{
    int i, _max = 0;
    n = read(); m = read(); P = read();
    For (i, 1, m)
        _u[i] = read(), _v[i] = read(),
        _max = max(_max, _cap[i] = read()),
        add_edge(_u[i], _v[i], _cap[i]);
    printf("%.0lf\n", Ans = solve());
    double l = 0, r = _max;
    while (r - l > 1e-5)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    printf("%.4lf\n", l * P);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值