醉熏熏的小颜

题意:
傲娇少年小颜是一个很帅很帅的小子,而且他非常非常地有爱心,很喜欢为幻想乡的人们做一些自己力所能及的事情来帮助他们。 这不,幻想乡突然发生了地震,所有的道路都崩塌了。现在的首要任务是尽快让幻想乡的交通体系重新建立起来。幻想乡一共有n个地方,那么最快的方法当然是修复n-1条道路将这n个地方都连接起来。 幻想乡这n个地方本来是连通的,一共有m条边。现在这m条边由于地震的关系,全部都毁坏掉了。每条边都有一个修复它需要花费的时间,第i条边所需要的时间为ei。地震发生以后,由于小颜是一位人生经验丰富,见得多了的长者,他根据以前的经验,知道每次地震以后,每个ei会是一个0到1之间均匀分布的随机实数。并且所有ei都是完全独立的。 现在小颜要出发去帮忙修复道路了,他可以使用一个神奇的大魔法,能够选择需要的那n-1条边,同时开始修复,那么修复完成的时间就是这n-1条边的ei的最大值。当然小颜会先使用一个更加神奇的大魔法来观察出每条边ei的值,然后再选择完成时间最小的方案。 小颜在走之前,她想知道修复完成的时间的期望是多少呢?

输入 1

10 10
0 2 3
2 3 2
3 1 3
1 2 1
1 0 1
1 1 0
3 3 0
1 2 2
3 1 1
3 1 0
3 1 2 2 3 1 1 2 2 0
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 0 1 0 0 0 0 0 0
1 0 0 0 1 0 0 0 0 0
1 0 1 0 0 0 0 1 1 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 1 0 0 0
0 0 0 0 0 1 0 0 1 0

输出 

8
42/1

上代码:

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
using ll = long long;
bool Mbe;
constexpr int N = 200 + 5;
constexpr int M = 2e3 + 5;
constexpr double eps = 1e-7;
constexpr double feps = 1e-8;
constexpr int inf = 0x1064822E;
// 为什么要给网络流再设一个 feps?
// 破案了,扰动的 eps 必须要在网络流上体现,否则会无限递归
// 除掉了一个 2a,所以理论上只要 eps / feps > 6 应该都没问题
// 确实是这样,feps = 1.6e-8 可以,但 1.7e-8 就爆炸了!
struct frac {
  ll a, b;
  frac(ll x = 0, ll y = 1) {ll d = __gcd(x, y); a = x / d, b = y / d;}
  bool operator == (const frac &z) const {return a * z.b == b * z.a;}
  frac operator + (const frac &z) const {return frac(a * z.b + b * z.a, b * z.b);}
  frac operator - (const frac &z) const {return frac(a * z.b - b * z.a, b * z.b);}
  frac operator * (const frac &z) const {return frac(a * z.a, b * z.b);}
  frac operator / (const frac &z) const {return frac(a * z.b, b * z.a);}
  double get() {return 1.0 * a / b;}
  void print() {cout << a << "/" << b;}
} ans;
struct flow {
  int cnt = 1, hd[N], nxt[M << 1], to[M << 1];
  double limit[M << 1];
  void clear() {cnt = 1, memset(hd, 0, sizeof(hd));}
  void add(int u, int v, double w) {
    nxt[++cnt] = hd[u], hd[u] = cnt, to[cnt] = v, limit[cnt] = w;
    nxt[++cnt] = hd[v], hd[v] = cnt, to[cnt] = u, limit[cnt] = 0;
  }
  int dis[N], cur[N], T;
  double dfs(int id, double res) {
    if(id == T) return res;
    double flow = 0;
    for(int i = cur[id]; i && res > feps; i = nxt[i]) {
      cur[id] = i;
      int it = to[i];
      double c = min(res, limit[i]);
      if(dis[id] + 1 == dis[it] && c > feps) {
        double k = dfs(it, c);
        flow += k, res -= k, limit[i] -= k, limit[i ^ 1] += k;
      }
    }
    return flow;
  }
  void maxflow(int s, int t) {
    T = t;
    while(1) {
      queue<int> q;
      memset(dis, -1, sizeof(dis));
      memcpy(cur, hd, sizeof(cur));
      q.push(s), dis[s] = 0;
      while(!q.empty()) {
        int t = q.front();
        q.pop();
        for(int i = hd[t]; i; i = nxt[i])
          if(dis[to[i]] == -1 && limit[i] > feps)
            dis[to[i]] = dis[t] + 1, q.push(to[i]);
      }
      if(dis[t] == -1) return;
      dfs(s, 1064);
    }
  }
};
int n, m, t;
int a[N], b[N], c[N], d[N], e[N][N];
// 开导!找到瞬时费用 = x 时对应最大流的直线
auto dinic(double lambda) {
  static flow g;
  g.clear();
  for(int i = 1; i <= n; i++)
    if(b[i] < lambda) {
      if(!a[i]) g.add(0, i, c[i]); // 特判 a[i] = 0
      else g.add(0, i, min(1.0 * c[i], (lambda - b[i]) / 2 / a[i]));
    }
  for(int i = 1; i <= n; i++)
    for(int j = 1; j <= m; j++)
      if(e[i][j])
        g.add(i, n + j, inf);
  for(int i = 1; i <= m; i++) g.add(n + i, t, d[i]);
  g.maxflow(0, t);
  frac K, B; // flow = K * lambda + B
  for(int i = 1; i <= n; i++)
    if(b[i] < lambda && g.dis[i] == -1) { // s -> i 之间的边被割掉
      if(!a[i]) B = B + frac(c[i]);
      else if(2 * a[i] * c[i] + b[i] < lambda) B = B + frac(c[i]); // 彻底满流,对最大流产生固定 c 贡献
      else K = K + frac(1, 2 * a[i]), B = B - frac(b[i], 2 * a[i]);
      // 没有彻底满流(在图上满流,但没有流满实际限制),产生直线 (lambda - b) / 2a 的贡献
    }
  for(int i = 1; i <= m; i++) if(g.dis[n + i] != -1) B = B + frac(d[i]); // == -> !=
  // n + i -> t 之间的边被割掉了,满流,最大流要加上这些贡献
  return make_pair(K, B);
}
vector<frac> pt;
void solve(auto l, auto r) {
  if(l == r) return; // 如果端点处两条直线重合,说明区间内没有顶点
  frac lambda = (r.se - l.se) / (l.fi - r.fi); // 求交点横坐标
  auto mid = dinic(lambda.get() + eps); // 扰动求解交点右侧的直线
  if(mid == r) return pt.push_back(lambda), void(); // 如果交点右侧直线和区间右端直线相同,说明 lambda 是唯一顶点
  solve(l, mid), solve(mid, r); // 否则向下分治处理子区间
}
bool Med;
int main() {
  fprintf(stderr, "%.4lf MB\n", (&Mbe - &Med) / 1048576.0);
  #ifdef ALEX_WEI
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
  #endif
  ios::sync_with_stdio(0);
  cin >> n >> m, t = n + m + 1;
  for(int i = 1; i <= n; i++) cin >> a[i] >> b[i] >> c[i];
  for(int i = 1; i <= m; i++) cin >> d[i];
  for(int i = 1; i <= n; i++)
    for(int j = 1; j <= m; j++)
      cin >> e[i][j];
  pt.push_back(frac()); // 原点搞进去
  for(int d : {1, 2, 3}) {
    solve(dinic(d - 1 + eps), dinic(d - eps));
    pt.push_back(frac(d)); // 先求区间内部顶点,再强制 i 为断点
    // 断点产生因为从 lambda - eps 到 lambda + eps,所有 a[i] = 0,b[i] = lambda 的边从零流变成满流
  }
  solve(dinic(3 + eps), dinic(inf));
  for(int i = 1; i < pt.size(); i++) { // 不许导,积回去!
    auto l = dinic(pt[i].get() - eps), r = dinic(pt[i].get() + eps);
    ans = ans + pt[i] * (r.se - l.se + (r.fi - l.fi) * pt[i]);
    // 将跳跃的流量乘以费用加进去,这部分是标准矩形长乘宽,只会在断点 1, 2, 3 处产生贡献
    // 长为横坐标 pt[i],宽为纵坐标之差,即 pt[i] 在左右两侧直线取值之差
    ans = ans + (pt[i - 1] + pt[i]) * (pt[i] - pt[i - 1]) * l.fi * frac(1, 2);
    // 梯形面积公式,pt[i] + pt[i - 1] 为上底 + 下底,(pt[i] - pt[i - 1]) * l.fi 为纵坐标之差,即高
    // 求出直线 l 左侧与 y 轴围成的梯形面积
  }
  cout << dinic(inf).se.get() << "\n";
  ans.print(), cout << "\n";
  return cerr << "Time: " << 1e3 * clock() / CLOCKS_PER_SEC << " ms\n", 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值