Codeforces - Fakes and Shidget (二分答案) (Samara Farewell Contest 2020 prob. B) 题解

一道不难想但是很恶心的二分答案题

提前%dalao
这个题在跟庆神的激烈讨论中逐渐成熟,感谢庆神大晚上跟我思考那么久

原题链接

题目截图
Codeforces - Fakes and Shidget (二分答案) (Samara Farewell Contest 2020 prob. B) 题解
题目描述
给定了n个NPC,每一个NPC有两个任务,任务有两个属性,时间和收益. pavel随机等可能性地遇见其中的一个NPC,在给的两个任务中完成一个,在完成后不休止继续重复上面的过程. 求在无限长时间里的单位时间收益最大值.

题目分析和解决
这题目看上去是需要用一些概率相关的知识,但是其实跟概率论没有多大的关系.

看懂题面之后的首要任务是看懂样例, 在截图中的样例里,并不是单纯的选择每一个NPC的收益速率高的任务. 因为如果选择(1,10)和(10,20), 在等可能完成两个任务时,单位时间收益的期望是 1 2 ∗ 10 + 1 2 ∗ 20 1 2 ∗ 1 + 1 2 ∗ 10 = 5 2 < 6.4545 = 71 11 \frac{\frac{1}{2} * 10 + \frac{1}{2}*20}{\frac{1}{2} * 1 + \frac{1}{2}*10} = \frac{5}{2} < 6.4545 = \frac{71}{11} 21​∗1+21​∗1021​∗10+21​∗20​=25​<6.4545=1171​
样例中的答案是选择了(10,70)和(1,1)的任务才得到的,这是因为想要通过选择(1,10)的任务,在11分钟里达到至少跟(10,70)一样的收益,至少需要在11次选择NPC里见到7次NPC1,这个概率是 ∑ i = 7 11 C 11 i ( 1 2 ) i ( 1 2 ) 11 − i = 562 2048 < 0.5 \sum_{i = 7}^{11}{C^i_{11}(\frac{1}{2})^{i} (\frac{1}{2})^{11-i}} = \frac{562}{2048}<0.5 i=7∑11​C11i​(21​)i(21​)11−i=2048562​<0.5 所以不如直接选择(10,70)收益更高

通过上面的分析我们也可以看到,代表概率论部分的 1/2 部分全部都可以被约掉,所以题目就转变为 求 ( ∑ 选 择 的 方 案 的 钱 数 ∑ 选 择 的 方 案 的 时 间 ) 的 最 大 值 g 求(\frac{\sum{选择的方案的钱数}}{\sum{选择的方案的时间}})的最大值g 求(∑选择的方案的时间∑选择的方案的钱数​)的最大值g

同时也不难知道,g必定至少为全选ab方案的结果,同时不可能超过所有单个NPC任务中的收益效率最大值,由此我们可以确定二分答案的上下界。

判断二分答案是否合法的部分是我与庆神研究时间最长的部分。
最开始我们提出想要判断一个当前的答案curV是否合法,只需要看选择的方案能不能组合出更大的速率,那么只需要判断对于当前的NPC,假设其他NPC的方案得到的效率就是curV, 而通过选择当前NPC的任务能不能让效率增加。
就是假设 c u r V = p q , p , q ∈ Z curV = \frac{p}{q},p,q\in Z curV=qp​,p,q∈Z
那么只需要不断地选择 p + g o l d q + t i m e > p q \frac{p+gold}{q+time}>\frac{p}{q} q+timep+gold​>qp​的方案就可以了
也就是说只要在每个方案中都选择 p + b i q + a i 和 p + d i q + c i \frac{p+b_i}{q+a_i} 和 \frac{p+d_i}{q+c_i} q+ai​p+bi​​和q+ci​p+di​​中较大的方案,就能让组合出来的速率保持在比curV更大或更接近的数值上,那么判断这个数值是否>=curV即可。
但是这样的话对需要判断的不等式进行化简 p + b i q + a i > p + d i q + c i \frac{p+b_i}{q+a_i} > \frac{p+d_i}{q+c_i} q+ai​p+bi​​>q+ci​p+di​​ p ( c i − a i ) + q ( b i − d i ) + b i c i − d i a i > 0 p(c_i - a_i) + q(b_i - d_i) + b_ic_i - d_ia_i>0 p(ci​−ai​)+q(bi​−di​)+bi​ci​−di​ai​>0 c u r V ( c i − a i ) + ( b i − d i ) + b i c i − d i a i q > 0 curV(c_i - a_i) + (b_i - d_i) + \frac{b_ic_i - d_ia_i}{q}>0 curV(ci​−ai​)+(bi​−di​)+qbi​ci​−di​ai​​>0尽管前面部分可以算出,后面避免不了需要求q,而后面的部分不能保证选择当满足上面的大于的式子的时候恒正(例如p=q=1,a=1,b=10,c=10,d=70),所以无法省略,不可以放缩成 c u r V ( c i − a i ) + ( b i − d i ) > 0 curV(c_i - a_i) + (b_i - d_i)>0 curV(ci​−ai​)+(bi​−di​)>0
此路失败

所以我们更换思路,要让 ∑ 1 n g o l d i ∑ 1 n t i m e i > c u r V \frac{\sum_{1}^{n}gold_i}{\sum_{1}^{n}time_i} >curV ∑1n​timei​∑1n​goldi​​>curV
只需要 ∑ 1 n g o l d i > c u r V ∑ 1 n t i m e i \sum_{1}^{n}gold_i>curV\sum_{1}^{n}time_i 1∑n​goldi​>curV1∑n​timei​
也就是 ∑ 1 n ( g o l d i − c u r V ∗ t i m e i ) > 0 \sum_{1}^{n}(gold_i - curV*time_i)>0 1∑n​(goldi​−curV∗timei​)>0
那么对于每一个NPC,选择 ( g o l d i − c u r V ∗ t i m e i ) (gold_i - curV*time_i) (goldi​−curV∗timei​)更大的方案无疑是相对curV来说更优秀的。所以判断函数就可喜可贺的完成了。
完成了判断函数这个题就啥都完事了
。。。。。。。。
当然不是
如果完全按照上面的思路写,得到的结果是Codeforces - Fakes and Shidget (二分答案) (Samara Farewell Contest 2020 prob. B) 题解
当然不排除我二分写得不好的可能性
我们再来算一笔账
想要达到1e-9的精度最多需要二分多少次
假设下界为0,上界为1e18
那么 1 e 18 2 x < 1 e − 9 \frac{1e18}{2^x}<1e-9 2x1e18​<1e−9 1 e 27 < 2 x 1e27<2^x 1e27<2x 27 l o g 2 10 < x 27log_210<x 27log2​10<x 27 l o g 2 10 < x 27log_210<x 27log2​10<x 89.692 < x 89.692<x 89.692<x
就是最多二分不到100次,就能达到精度要求
保险起见,我们把它乘7 (只要不TLE就往死里算) 限制循环最多700次,可以通过本题

AC代码
贴上我无比丑陋的代码供大家喷

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

const int MAXX = 2e5+100;
const double ESP = 1e-10;
const int timeLimit = 700;


long long a[MAXX] , b[MAXX] , c[MAXX] , d[MAXX];



bool Check(double curV, int n)
{
	double ans = 0;
	
	for (int i = 1;i <= n;++i)
		ans += max(b[i] - curV * a[i], d[i] - curV * c[i]);
		
	return ans >= 0;
}
int main()
{
	int n;
	scanf("%d" , &n);
	
	double rightLimit = 0;
	long long betterTime , betterCoin;
	
	for (int i = 1;i <= n;++i){
		
		scanf("%lld%lld%lld%lld",&a[i],&b[i],&c[i],&d[i]);
		
		betterTime += a[i];
		betterCoin += b[i];
		
		rightLimit = max(rightLimit , max(1.0 * b[i] / a[i], 1.0 * d[i] / c[i]));
	}
	
	double leftLimit = 1.0 * betterCoin / betterTime;
	
	int cnt = 0;
	while (rightLimit - leftLimit > ESP && cnt < timeLimit){
		
		double mid = (rightLimit + leftLimit) / 2;
		
		cnt++;
		
		if (Check(mid,n))
			leftLimit = mid + ESP;
		else 
			rightLimit = mid;
			
	}
	
	printf("%.15lf",rightLimit);
	return 0;
}
上一篇:Samara Farewell Contest 2020 (XXI Open Cup, GP of Samara) D. Two Pirates - 2


下一篇:QLU Regular Contest 003 题解