首页 > 其他分享 >LOJ 6479 [ICPC World Finals 2017] 小小水管工 Son of Pipe Stream 题解

LOJ 6479 [ICPC World Finals 2017] 小小水管工 Son of Pipe Stream 题解

时间:2023-09-28 16:13:53浏览次数:38  
标签:6479 le Stream int 题解 Flubber double edge max

更好的阅读体验

题意

原题链接

  • 给出 \(n\) 个城市和 \(m\) 条双向管道,以及两个实数 \(v\) 和 \(a\)。有两种液体,分别是水和 Flubber(下面简写为 W 和 F)。\(1\) 号和 \(2\) 号城市分别生产 Flubber 和水,并通过管道流入 \(3\) 号城市。对于一条管道,其中可以同时存在两种液体,但是方向必须相同。每条管道有一个容量 \(c_i\),如果一条管道中有 \(w\) 个单位的水和 \(f\) 个单位的 Flubber,那么需要满足 \(v\cdot f + w \le c_i\)。记最终流到 \(3\) 号城市的水和 Flubber 分别为 \(W\) 和 \(F\),求 \(F^a\cdot W^{1-a}\) 的最大值,并给出任意一个方案。
  • \(n \le 200\),\(n - 1 \le m \le \frac{n(n - 1)}{2}\),\(1\le v,c_i\le 10\),\(0.01\le a\le 0.99\)。

英文题解

做法

首先 \(v\) 是没有用的,因为我们可以把所有的 Flubber 都乘上 \(v\),最后把答案除以 \(v^a\),这样就把 \(v\) 去掉了。

考虑如果我们不区分两种液体(即两个源点都可以产出两种液体),那么这就是一个普通最大流。设此时的流量为 \(Z\)。

现在考虑区分两种液体怎么做。
设两种液体的流量分别为 \(F\) 和 \(W\),那么容易发现 \(F+W\le Z\)。那是不是 \(F\) 的取值范围就是 \([0, Z]\) 呢?
很容易发现这是错的。假设我们只保留 Flubber,设此时的最大流为 \(F_{\max}\),类似地定义 \(W_{\max}\),那么容易发现 \(F\) 的取值范围其实应该是 \([Z-W_{\max},F_{\max}]\)。

接下来就是人类智慧。感性理解一下我们能够发现,似乎对于任意一个 \(F\in [Z-W_{\max}, F_{\max}]\),都存在一种方案使得 \(F+W=Z\)(我们先不考虑 Flubber 和水的方向不能相同的限制)。

证明

我们任取一个 \(F=F_{\max}\) 的方案,记为 \(\overrightarrow f\);然后任取一个 \(W=W_{\max}\) 的方案,记为 \(\overrightarrow w\)。
因为在 \(\overrightarrow f\) 中 \(F=F_{\max}\),而在 \(\overrightarrow w\) 中 \(F=Z-W_{\max}\),而我们知道 \(F\in[Z-W_{\max}, F_{\max}]\),所以必然存在实数 \(\alpha\in[0,1]\) 使得 \(\alpha F_{\max} + (1-\alpha)(Z-W_{\max}) = F\),那么此时 \(\alpha\overrightarrow f+(1-\alpha)\overrightarrow w\) 就是流量为 \(F\) 的一个方案。

现在问题转化为,对于任意 \(F\) 满足 \(F\in[Z-W_{\max}, F_{\max}]\),求 \(F^a\cdot (Z-F)^{1-a}\) 的最大值。简单求导发现应该取 \(F=a\cdot Z\)。如果 \(a\cdot Z\) 不在 \([Z-W_{\max}, F_{\max}]\) 内,就取区间内离 \(a\cdot Z\) 最近的点就好了。记这个点为 \(F^*\),并记 \(Z-F^*=W^*\)。

求导过程

记 \(G(F) = F^a(Z-F)^{1-a}\),那么

\[\begin{aligned} &G'(F)\\ =&(F^a(Z-F)^{1-a})'\\ =&(F^a)'\cdot (Z-F)^{1-a}+((Z-F)^{1-a})'\cdot F^a\\ =&aF^{a-1}(Z-F)^{1-a}+(a-1)(Z-F)^{-a}F^a\\ =&F^{a-1}(Z-F)^{-a}(a(Z-F)+(a-1)F)\\ =&F^{a-1}(Z-F)^{-a}(aZ-F) \end{aligned} \]

因为 \(G(F)\) 在定义域内连续,所以极值点即为所有 \(G'(F)=0\) 的点,即 \(0,Z,aZ\) 三个点。代入原式发现 \(0,Z\) 都是最小值,所以最大值就是 \(F=aZ\)。

现在答案已经求出来了,但是还要构造方案。注意到我们一直没考虑水和 Flubber 的方向不能相反的限制,但其实这不影响,只要我们最终构造出来的方案满足这个限制就行了。
求方案其实也比较简单。首先我们给 \(1\) 号点限制 \(F^*\) 的流量,给 \(2\) 号点限制 \(W^*\) 的流量,跑一个最大流,这样可以求出最优解中每条边的方向和总流量。但是我们没法把 Flubber 和水区分开。
于是我们建一个新的图,点还是原图的点,但是边的方向和流量为最优解中这条边的方向和流量。那么这个图其实本来就是一个最大流。然后我们 给 \(1\) 号点 \(F^*\) 的流量,那么此时的最大流就是 Flubber 的方案。这样会不会导致水的方案出问题呢?其实不会,因为一个合法流减去它的子流还是一个合法流。而且这个方案也满足水和 Flubber 的方向不能相反的限制。

那么这道题就做完了,感觉难点在于这个结论和找方案。

代码

代码
#include <bits/stdc++.h>

const int N = 200 + 5;
const int M = N * N;
const int FLOW_N = N + 4;
const int FLOW_M = N * N * 2 + N + 2;
const double eps = 1e-8;
const double FINF = 1e18;

int n, m;
struct GraphEdge { int u, v; double w; } e[M];
double fe[M];
double V, A;

struct Dinic {
	struct Edge { int to, nxt; double r; } edge[FLOW_M << 1];
	int head[FLOW_N], cur[FLOW_N], ek;
	int n, s, t;
	int dep[FLOW_N];
	std::queue<int> q;
	void add_one_edge(int u, int v, double c) { edge[ek] = (Edge){v, head[u], c}, head[u] = ek++; }
	bool bfs() {
		for(int i = 1; i <= n; i++) dep[i] = 0;
		dep[s] = 1, q.push(s);
		while(!q.empty()) {
			int u = q.front();
			q.pop();
			for(int i = head[u]; i; i = edge[i].nxt) if(!dep[edge[i].to] && edge[i].r > eps) {
				int v = edge[i].to;
				dep[v] = dep[u] + 1;
				q.push(v);
			}
		}
		return dep[t];
	}
	double dfs(int u, double in) {
		if(u == t) return in;
		double out = 0;
		for(int &i = cur[u]; i; i = edge[i].nxt) if(dep[u] + 1 == dep[edge[i].to] && edge[i].r > eps) {
			int v = edge[i].to;
			double ret = dfs(v, std::min(in, edge[i].r));
			if(ret < eps) continue;
			edge[i].r -= ret, edge[i ^ 1].r += ret;
			in -= ret, out += ret;
			if(in < eps) return out;
		}
		if(out < eps) dep[u] = 0;
		return out;
	}
	Dinic() : ek(2) {}
	void init(int n_) { n = n_; ek = 2; for(int i = 1; i <= n; i++) head[i] = 0; }
	void add_edge(int u, int v, double c) { add_one_edge(u, v, c), add_one_edge(v, u, 0); }
	double maxflow(int s_, int t_) {
		s = s_, t = t_;
		double ret = 0;
		while(bfs()) {
			for(int i = 1; i <= n; i++) cur[i] = head[i];
			ret += dfs(s, FINF);
		}
		return ret;
	}
} dinic;

int main() {
	scanf("%d%d%lf%lf", &n, &m, &V, &A);
	for(int i = 1; i <= m; i++) scanf("%d%d%lf", &e[i].u, &e[i].v, &e[i].w);
	int src = n + 1, dst = n + 2;
	dinic.init(n + 2);
	for(int i = 1; i <= m; i++) dinic.add_edge(e[i].u, e[i].v, e[i].w), dinic.add_edge(e[i].v, e[i].u, e[i].w);
	dinic.add_edge(src, 2, FINF), dinic.add_edge(src, 1, FINF), dinic.add_edge(3, dst, FINF);
	double flow = dinic.maxflow(src, dst);
	dinic.init(n + 2);
	for(int i = 1; i <= m; i++) dinic.add_edge(e[i].u, e[i].v, e[i].w), dinic.add_edge(e[i].v, e[i].u, e[i].w);
	dinic.add_edge(src, 1, FINF), dinic.add_edge(3, dst, FINF);
	double Fmax = dinic.maxflow(src, dst);
	dinic.init(n + 2);
	for(int i = 1; i <= m; i++) dinic.add_edge(e[i].u, e[i].v, e[i].w), dinic.add_edge(e[i].v, e[i].u, e[i].w);
	dinic.add_edge(src, 2, FINF), dinic.add_edge(3, dst, FINF);
	double Wmax = dinic.maxflow(src, dst);
	double F = std::max(std::min(A * flow, Fmax), flow - Wmax);
	double W = flow - F;
	// printf("flow = %.3f, Fmax = %.3f, Wmax = %.3f, F = %.3f\n", flow, Fmax, Wmax, F);
	dinic.init(n + 2);
	for(int i = 1; i <= m; i++) dinic.add_edge(e[i].u, e[i].v, e[i].w), dinic.add_edge(e[i].v, e[i].u, e[i].w);
	dinic.add_edge(src, 2, W), dinic.add_edge(src, 1, F), dinic.add_edge(3, dst, FINF);
	double ff1 = dinic.maxflow(src, dst);
	assert(std::abs(ff1 - flow) <= eps);
	for(int i = 2; i <= 4 * m + 1; i += 4) fe[(i + 2) / 4] = dinic.edge[i ^ 1].r - dinic.edge[(i + 2) ^ 1].r;
	// for(int i = 1; i <= m; i++) printf("(%d, %d) %.4f\n", e[i].u, e[i].v, fe[i]);
	dinic.init(n + 2);
	for(int i = 1; i <= m; i++) dinic.add_edge(e[i].u, e[i].v, std::max(fe[i], 0.)), dinic.add_edge(e[i].v, e[i].u, std::max(-fe[i], 0.));
	dinic.add_edge(src, 1, F), dinic.add_edge(3, dst, FINF);
	double ff2 = dinic.maxflow(src, dst);
	// printf("ff = %.3f\n", ff2);
	assert(std::abs(ff2 - F) <= eps);
	for(int i = 2; i <= 4 * m + 1; i += 4) {
		double f = dinic.edge[i ^ 1].r - dinic.edge[(i + 2) ^ 1].r;
		printf("%.11f %.11f\n", f / V, fe[(i + 2) / 4] - f);
	}
	printf("%.11f\n", pow(F / V, A) * pow(flow - F, 1 - A));
	return 0;
}

参考资料:

https://www.csc.kth.se/~austrin/icpc/finals2017solutions.pdf
https://cekavis.github.io/icpc-world-finals-2017/

标签:6479,le,Stream,int,题解,Flubber,double,edge,max
From: https://www.cnblogs.com/xxeray/p/loj-6479-solution.html

相关文章

  • Jenkins问题解决_控制台输出:Windows下中文乱码,文本方式查看显示正常
    背景使用Git克隆代码时出现错误,控制台输出内容为中文乱码,文本方式查看显示正常Jenkins版本:2.423原因Jenkins内JAVA编码设置问题查看jenkins编码格式系统管理——>系统信息,查找sun.jnu.encoding字段。如果不是UTF-8,就可能导致中文支持有问题(GBK等支持度不够)。解决设......
  • 题解 [HEOI2016/TJOI2016] 排序
    题目链接看到这道题按照套路首先想到二分答案(即二分\(q\)位置上的数,记作\(mid\))。再按照套路将大于\(mid\)的数字设为\(1\),将等于\(mid\)的数设为\(2\),小于\(mid\)的数字设为\(0\)。那么对于区间\([l,r,0]\)操作,应该先讲\(0,1,2\)的数量找出来,然后按照从小到大......
  • java用Stream一行代码实现数据分组统计、排序、最大值、最小值、平均值、总数、合计
    getAverage():它返回所有接受值的平均值。getCount():它计算所有元素的总数。getMax():它返回最大值。getMin():它返回最小值。getSum():它返回所有元素的总和。示例:@GetMapping("/list")publicvoidlist(){List<InputForm>inputForms=inputFormMapper.se......
  • 题解 CF1873H【Mad City】
    其他题解怎么又Tarjan又Dijkstra的,这是div4H的样子吗,来个简单好写的做法。题面里的人名太复杂了,本题解中称为警察和小偷。注意到,如果小偷成功到达了环上,那么一定不会被警察抓到。因为小偷知道警察下一步会走到哪里,他可以执行相同的操作(顺时针/逆时针/静止),使得他和警察之间......
  • [ARC124C] LCM of GCDs 题解
    题面给定\(N\)个正整数对\((a_i,b_i)\)和两个初始为空的集合\(S,T\),你可以选择将每个数对的两个元素划分到两个不同的集合中。求\[\max\operatorname{lcm}(\gcd\limits_{x\inS}x,\gcd\limits_{y\inT}y)\](\(1\leN\le50,1\lea_i,b_i\le10^9\))。题解首先,......
  • P4370 [Code+#4] 组合数问题2-题解-有关对数的小技巧
    20230927P4370[Code+#4]组合数问题2-solStatement传送门给你两个数\(n,k\),要求对于组合数\(C_{a}^{b}\)找到任何\(k\)个,让他们的和最大,且组合数各不相同,当且仅当\(a,b\)不完全相同时,组合数不同。Solution首先,很容易发现\(C_{n}^{m}\gtc_{n-1}^{m}\),所......
  • CF364D Ghd 题解
    CF364DGhd题解题目大意给定一个长度为\(n\)的序列,你需要从中选出一个元素个数不少于\(\left\lceil{\frac{n}{2}}\right\rceil\)的子序列,使得这个子序列中所有元素的\(\gcd\)最大。分析数据范围吓人。\(10^6\),但是根本想不到什么\(O(n\logn)\)或\(O(n)\)的算法......
  • [ARC125B] Squares 题解
    题意给定正整数\(N\),求满足如下条件的正整数对\((x,y)\)的数量:\(1\lex,y\leN\)\(x^2-y\)为完全平方数(\(0\)也是完全平方数)(\(1\leN\le10^{12}\))。题解因为\(x^2-y\)为完全平方数,设其为\(z^2\),那么有\[\begin{aligned}x^2-y=z^2\\\end{al......
  • [题解] Codeforces Round 900(Div.3) E~F
    CodeforcesRound900(Div.3)E~FE.Iva&Pav因为按位与的结果不会随着越多数字的增加而增加,因此我们可以利用这个性质二分出右端点,只需要一个可以查询区间的数据结构即可。或者是按位考虑第\(i\)个数字的第\(k\)位,后缀最近的\(0\)的位置,按位考虑也可以。但是这题使用二分......
  • ACAM 学习笔记 | 附 YbtOJ 全部题解
    怎么有人现在才学ACAM呢。好像比SAM简单挺多啊,也不记得当时是哪里看不懂。AC自动机(✔)自动AC机(✘)概述ACAM(Aho–CorasickAutomaton),是用来解决多模式串匹配的字符串算法。它的结构是个DAG,其中点表示状态,边表示转移。这一点上各种自动机都是相同的。具体来说,可以感性......