题意:
傲娇少年小颜是一个很帅很帅的小子,而且他非常非常地有爱心,很喜欢为幻想乡的人们做一些自己力所能及的事情来帮助他们。 这不,幻想乡突然发生了地震,所有的道路都崩塌了。现在的首要任务是尽快让幻想乡的交通体系重新建立起来。幻想乡一共有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;
}