1 条题解
-
0
自动搬运
来自洛谷,原作者为

hehezhou
**搬运于
2025-08-24 21:47:57,当前版本为作者最后更新于2019-06-05 12:52:21,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
可以在我的博客食用
抱歉上次链接挂错了
题面
不得不说,浙江神队选拔赛真是年年毒瘤
首先你需要有一点导数的知识,否则可能看不懂
第一问很简单,建个图跑个最大流就好了(如果这都不会还敢来肝zjoi???)
第二问-
有一个很的想法:
因为费用不是一次函数,而是二次函数,并且我们观察到
所以我先想到的是把每个酿酒点的费用强行离散,就是分成一个个区间,然后源点向每个酿酒点连很多条边,再跑费用流
因为导函数单调不降,所以理论上是可以做到一定精度的
然而我们观察到毒瘤出题人要求输出分数 -
让误差降为0???
显然分的越细误差越小
考虑分的份数无限趋于0
此时费用即为导数
然后就可以开个集合维护当前单位费用最小的酿酒点
然后二分出当单位费用涨到多少时,集合内点会变化(要么是有新点加入,要么是有一个点跑满流)
然后更新集合并进行下一轮操作
然而二分还是只能做到一定精度(貌似乘上一个奥妙重重的系数可以二分整数,但我不知道是不是对的,而且复杂度好像是假的) -
正解! 观察一下解法2,就会发现当集合内点不变时,产酒量(当前流量)和费用呈线性关系,并且当集合内点变化时,费用的一次函数会发生变化,最多变化次
所以搞一个函数表示单位费用(导数)不超过x时,最大流是多少
那么可以被分成若干段,每段均为一次函数,最多段
然后可以用类似积分的方式搞出最小费用
考虑怎么搞出这个函数
如果只有二次函数的费用,那么f(x)会形成一个类似上凸壳的图像
然后考虑分治,每次求出最左一次函数和最右一次函数的交点,如果再图像上则该点是区间内唯一断点,否则递归处理两个子区间
然后考虑一次函数
发现b只会是0, 1, 2, 3(详细数据范围见bzoj)
只要强行设1, 2, 3为断点即可
记得加上左右边界的f(x)之差(见代码) 复杂度网络流#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef double db; const db feps = 1e-11, eps = 1e-9; inline ll gcd(ll a, ll b) { return a == 0 || b == 0 ? a | b : gcd(b, a % b); } struct frac { ll a, b; //仔细看题发现要开ll inline frac(ll _a = 0, ll _b = 1) { int g = gcd(_a, _b); a = _a / g, b = _b / g; } friend inline frac operator + (frac a, frac b) { return frac(a.a * b.b + a.b * b.a, a.b * b.b); } friend inline frac operator - (frac a, frac b) { return frac(a.a * b.b - a.b * b.a, a.b * b.b); } friend inline frac operator * (frac a, frac b) { return frac(a.a * b.a, a.b * b.b); } friend inline frac operator / (frac a, frac b) { return frac(a.a * b.b, a.b * b.a); } inline operator db () { return 1.0l * a / b; } }; int dis[210], s, t; int a[110], b[110], c[110], d[110], ed[110][110]; struct edge { db f; int v, nxt; } e[3010]; int tot, head[210], cur[210]; int n, m; inline void addedge(int u, int v, db c) { e[++tot] = edge{c, v, head[u]}; head[u] = tot; e[++tot] = edge{0, u, head[v]}; head[v] = tot; } inline int bfs() { queue<int> q; memset(dis, -1, sizeof dis); dis[s] = 0; q.push(s); memcpy(cur, head, sizeof head); while(!q.empty()) { int now = q.front(); q.pop(); for(int i = head[now]; i; i = e[i].nxt) { if(~dis[e[i].v] || e[i].f < feps) continue; dis[e[i].v] = dis[now] + 1; q.push(e[i].v); } } return dis[t] != -1; } inline db dfs(int now, db limit) { if(now == t) return limit; db ans = 0; for(int &i = cur[now]; i; i = e[i].nxt) { if(e[i].f < feps || dis[e[i].v] != dis[now] + 1) continue; db lala = dfs(e[i].v, min(limit, e[i].f)); limit -= lala, ans += lala; e[i].f -= lala, e[i ^ 1].f += lala; if(limit < feps) return ans; } return ans; } inline pair<frac, frac> dinic(db lambda) { tot = 1; memset(head, 0, sizeof head); for(int i = 1; i <= n; i++) { if(a[i] == 0) { if(b[i] < lambda) addedge(s, i, c[i]); } else { db w = (lambda - b[i]) / 2 / a[i]; if(w > feps) addedge(s, i, min(1.0 * c[i], w)); } } for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) if(ed[i][j]) addedge(i, j + n, 1e9); for(int i = 1; i <= m; i++) addedge(i + n, t, d[i]); while(bfs()) dfs(s, 1e9); frac K, B; for(int i = 1; i <= n; i++) { if(dis[i] == -1) { if(a[i] == 0) { if(b[i] < lambda) B = B + frac(c[i]); } else { if(b[i] > lambda) B = B + frac(); else if(a[i] * 2 * c[i] + b[i] > lambda) K = K + frac(1, a[i] * 2), B = B - frac(b[i], a[i] * 2); else B = B + frac(c[i]); } } } for(int i = 1; i <= m; i++) if(dis[i + n] != -1) B = B + frac(d[i]); return make_pair(K, B); } //dinic板子 vector<pair<frac, frac> > v; inline void solve(pair<frac, frac> fl, pair<frac, frac> fr) { //分治找断点 if(fl.first == fr.first && fl.second == fr.second) return; frac px = (fl.second - fr.second) / (fr.first - fl.first); pair<frac, frac> fml = dinic((db)px - eps), fmr = dinic((db)px + eps); if(fmr.first == fr.first && fmr.second == fr.second) { v.push_back(make_pair(px, fml.second + fml.first * px)); } else { solve(fl, fml); solve(fmr, fr); } } int main() { scanf("%d%d", &n, &m); s = 0, t = n + m + 1; for(int i = 1; i <= n; i++) scanf("%d%d%d", a + i, b + i, c + i); for(int i = 1; i <= m; i++) scanf("%d", d + i); for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) scanf("%d", &ed[i][j]); frac ans, sum; cout << (sum = dinic(1e9).second).a;//第一问 v.push_back(make_pair(frac(), 0)); for(int i = 1; i <= 3; i++) { auto l = dinic(i - 1 + eps), r = dinic(i - eps); solve(l, r); v.push_back(make_pair(frac(i), r.second + frac(i) * r.first)); } solve(dinic(3 + eps), dinic(1e9));//找断点 for(int i = 1; i < v.size(); i++) { auto l = dinic((db)v[i].first - eps), r = dinic((db)v[i].first + eps), _l = dinic((db)v[i - 1].first + eps);//std=c++11(滑稽 ans = ans + v[i].first * ((r.first - l.first) * v[i].first + r.second - l.second); ans = ans + (v[i].second - v[i - 1].first * _l.first - _l.second) * frac(1, 2) * (v[i].first + v[i - 1].first);//积分 } if(ans.a < 0) ans.a = -ans.a, ans.b = -ans.b; return cout << ans.a << '/' << ans.b << endl, 0; } -
- 1
信息
- ID
- 2420
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者