【二分查找】P10091 [ROIR 2022 Day 2] 分数排序|普及+

本文涉及的基础知识点

C++二分查找

[ROIR 2022 Day 2] 分数排序

题目背景

翻译自 ROIR 2022 D2T2

题目描述

有两个由 n n n 个不同整数组成的序列 A = [ a 1 , a 2 , ... , a n ] A = [a_1, a_2, \dots , a_n] A=[a1,a2,...,an] 和 B = [ b 1 , b 2 , ... , b n ] B = [b_1, b_2, \dots , b_n] B=[b1,b2,...,bn]。将它们组合成 n 2 n^2 n2 个分数,形式为 a i b j \frac{a_i}{b_j} bjai,并将每个分数约分后按递增顺序排序。

给定一个数字 q q q 和 q q q 个整数 c 1 , c 2 , ... , c q c_1, c_2, \dots , c_q c1,c2,...,cq。对于每个 c i c_i ci,请输出上面所说的 n 2 n^2 n2 个分数中第 c i c_i ci 小的分数。

输入格式

第一行包含两个整数 n n n 和 q q q。

第二行包含 n n n 个不同的整数 a 1 , a 2 , ... , a n a_1, a_2, \dots, a_n a1,a2,...,an。

第三行包含 n n n 个不同的整数 b 1 , b 2 , ... , b n b_1, b_2, \dots, b_n b1,b2,...,bn。

第四行包含 q q q 个不同的整数 c 1 , c 2 , ... , c q c_1, c_2, \dots, c_q c1,c2,...,cq。

输出格式

输出 q q q 行。第 i i i 行输出按递增顺序得到的第 c i c_i ci 个分数。分数 p q \frac{p}{q} qp 应以 p q 的格式输出,并且应为最简分数。

样例 #1

样例输入 #1

复制代码
4 8
3 4 1 2
2 3 4 5
1 16 2 4 5 6 10 15

样例输出 #1

复制代码
1 5
2 1
1 4
2 5
1 2
1 2
4 5
3 2

提示

在样例中,初始的分数列表如下:

3 2 , 3 3 , 3 4 , 3 5 , 4 2 , 4 3 , 4 4 , 4 5 , 1 2 , 1 3 , 1 4 , 1 5 , 2 2 , 2 3 , 2 4 , 2 5 \] , \\left\[ \\frac{3}{2}, \\frac{3}{3}, \\frac{3}{4}, \\frac{3}{5}, \\frac{4}{2}, \\frac{4}{3}, \\frac{4}{4}, \\frac{4}{5}, \\frac{1}{2}, \\frac{1}{3}, \\frac{1}{4}, \\frac{1}{5}, \\frac{2}{2}, \\frac{2}{3}, \\frac{2}{4}, \\frac{2}{5} \\right\], \[23,33,43,53,24,34,44,54,21,31,41,51,22,32,42,52\], 经过约分后,得到: \[ 3 2 , 1 1 , 3 4 , 3 5 , 2 1 , 4 3 , 1 1 , 4 5 , 1 2 , 1 3 , 1 4 , 1 5 , 1 1 , 2 3 , 1 2 , 2 5 \] , \\left\[ \\frac{3}{2}, \\frac{1}{1}, \\frac{3}{4}, \\frac{3}{5}, \\frac{2}{1}, \\frac{4}{3}, \\frac{1}{1}, \\frac{4}{5}, \\frac{1}{2}, \\frac{1}{3}, \\frac{1}{4}, \\frac{1}{5}, \\frac{1}{1}, \\frac{2}{3}, \\frac{1}{2}, \\frac{2}{5} \\right\], \[23,11,43,53,12,34,11,54,21,31,41,51,11,32,21,52\], 最后按递增顺序排序,得到: \[ 1 5 , 1 4 , 1 3 , 2 5 , 1 2 , 1 2 , 3 5 , 2 3 , 3 4 , 4 5 , 1 1 , 1 1 , 1 1 , 4 3 , 3 2 , 2 1 \] . \\left\[ \\frac{1}{5}, \\frac{1}{4}, \\frac{1}{3}, \\frac{2}{5}, \\frac{1}{2}, \\frac{1}{2}, \\frac{3}{5}, \\frac{2}{3}, \\frac{3}{4}, \\frac{4}{5}, \\frac{1}{1}, \\frac{1}{1}, \\frac{1}{1}, \\frac{4}{3}, \\frac{3}{2}, \\frac{2}{1} \\right\]. \[51,41,31,52,21,21,53,32,43,54,11,11,11,34,23,12\]. 本题使用捆绑测试。 | 子任务 | 分值 | 特殊性质 | |:-----:|:--------:|:-----------------------------------:| | 1 1 1 | 14 14 14 | n ≤ 50 n\\le50 n≤50 | | 2 2 2 | 13 13 13 | n ≤ 500 n\\le500 n≤500 | | 3 3 3 | 15 15 15 | q , c i ≤ 100 q,c_i\\le100 q,ci≤100 | | 4 4 4 | 21 21 21 | c i ≤ 10 5 c_i\\le10\^5 ci≤105 | | 5 5 5 | 37 37 37 | | 对于 100 % 100\\% 100% 的数据, 1 ≤ n ≤ 10 5 1 \\leq n \\leq 10\^5 1≤n≤105, 1 ≤ q ≤ 10 5 1 \\leq q \\leq 10\^5 1≤q≤105 且 q ≤ n 2 , n × q ≤ 10 5 q \\leq n\^2,n\\times q\\le10\^5 q≤n2,n×q≤105(所以实际上 q ≤ 1000 10 3 ≈ 2154 q\\le1000\\sqrt\[3\]{10}\\approx2154 q≤1000310 ≈2154), 1 ≤ a i , b i ≤ 10 6 1 \\leq a_i,b_i \\leq 10\^6 1≤ai,bi≤106, 1 ≤ c i ≤ n 2 1 \\leq c_i \\leq n\^2 1≤ci≤n2。 ## 二分查找 二分类型:寻找首端。 参数范围:\[0,1e6

迭代精度:1e-12,由于分母 ≤ \leq ≤ 1e6,如果分母相同,任意两个不同的分数差的绝对值大于等于1e-6。如果分母不同,任意两个分数差的绝对值大于等于1e-12。

时间复杂度:O(qlogTnlogn) T = e18

Check函数:小于等于mid的数量 >= ci。

后处理:大于等于ans,小于ans+e-12的数。

找到ai,bi后除以最大公约数。

超时

发现子任务一和子任务二超时,子任务三到无不超时。

子任务一二 n <=500。

故 n<=500暴力,n > 500二分。

cpp 复制代码
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>

#include <bitset>
using namespace std;

template<class T = int>
vector<T> Read() {
	int n;
	scanf("%d", &n);
	vector<T> ret(n);
	for(int i=0;i < n ;i++) {
		cin >> ret[i];
	}
	return ret;
}

template<class T = int>
vector<T> Read(int n) {
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}
string ReadChar(int n) {
	string str;
	char ch;
	while (n--) {
		do
		{
			scanf("%c", &ch);
		} while (('\n' == ch));
			str += ch;
	}
	return str;
}
template<class T1,class T2>
void ReadTo(pair<T1, T2>& pr) {
	cin >> pr.first >> pr.second;
}

template<class INDEX_TYPE>
class CBinarySearch
{
public:
	CBinarySearch(INDEX_TYPE iMinIndex, INDEX_TYPE iMaxIndex, INDEX_TYPE tol = 1) :m_iMin(iMinIndex), m_iMax(iMaxIndex), m_iTol(tol) {}
	template<class _Pr>
	INDEX_TYPE FindFrist(_Pr pr)
	{
		auto left = m_iMin - m_iTol;
		auto rightInclue = m_iMax;
		while (rightInclue - left > m_iTol)
		{
			const auto mid = left + (rightInclue - left) / 2;
			if (pr(mid))
			{
				rightInclue = mid;
			}
			else
			{
				left = mid;
			}
		}
		return rightInclue;
	}
	template<class _Pr>
	INDEX_TYPE FindEnd(_Pr pr)
	{
		INDEX_TYPE leftInclude = m_iMin;
		INDEX_TYPE right = m_iMax + m_iTol;
		while (right - leftInclude > m_iTol)
		{
			const auto mid = leftInclude + (right - leftInclude) / 2;
			if (pr(mid))
			{
				leftInclude = mid;
			}
			else
			{
				right = mid;
			}
		}
		return leftInclude;
	}
protected:
	const INDEX_TYPE m_iMin, m_iMax, m_iTol;
};


class Solution {
		public:	
			vector<pair<int, int>> Ans(vector<int>& a, vector<int>& b, vector<long long>& que) {
				auto fun =(a.size() <= 500) ? Ans1 : Ans2;
				return (*fun)(a, b, que);
			}
			static vector<pair<int,int>> Ans1(vector<int>&a,vector<int>&b,vector<long long>& que) {
				vector<pair<int, int>> tmp;
				const int N = a.size();
				for (int i = 0; i < N; i++) {
					for (int j = 0; j < N; j++)
					{
						const int iGCD = gcd(a[i], b[j]);
						tmp.emplace_back(a[i] / iGCD, b[j] / iGCD);
					}
				}
				sort(tmp.begin(), tmp.end(), [&](const pair<int, int>& p1, const pair<int, int>& p2)
					{return (long long)p1.first * p2.second<(long long)p2.first* p1.second; });
				vector<pair<int, int>> ans;
				for (const auto& c : que)
				{					
					ans.emplace_back(tmp[c-1]);
				}
				return ans;
			}
			static vector<pair<int, int>> Ans2(vector<int>& a, vector<int>& b, vector<long long>& que) {
				sort(a.begin(), a.end());
				sort(b.begin(), b.end());
				vector<pair<int, int>> ans;
				for (const auto& c : que)
				{
					auto Check = [&](double mid) {
						long long cnt = 0;
						for (const auto& ib : b) {
							cnt += upper_bound(a.begin(), a.end(), mid * ib) - a.begin();
						}
						return cnt >= c;
					};
					double dAns = CBinarySearch<double>(0, 1e6, 1e-12).FindFrist(Check);
					vector<tuple<double, int, int>> tmp;
					for (const auto& ib : b) {
						auto it = upper_bound(a.begin(), a.end(), dAns * ib);
						if (a.begin() != it) {
							--it;
							tmp.emplace_back(*it / (double)ib, *it, ib);
						}
					}
					nth_element(tmp.begin(), std::prev(tmp.end()), tmp.end());
					const int iGcd = gcd(get<1>(tmp.back()), get<2>(tmp.back()));
					ans.emplace_back(get<1>(tmp.back()) / iGcd, get<2>(tmp.back()) / iGcd);
				}
				return ans;
			}
		};
		
int main() {
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG
	int n,q;	
	cin >> n >> q ;
	auto a = Read<int>(n);
	auto b = Read<int>(n);
	auto que = Read<long long>(q);
#ifdef _DEBUG		
	/*Out(a, "a=");
	Out(b, ",b=");
	Out(que, ",que=");*/
#endif	
	auto res = Solution().Ans(a, b, que);
	for (const auto& [i, j] : res)
	{
		cout << i <<" " <<j  << endl;
	}
	return 0;
}

单元测试

cpp 复制代码
	vector<int> a, b;
		vector<long long> que;
		TEST_METHOD(TestMethod11)
		{
			a = { 3,4,1,2 }, b = { 2,3,4,5 }, que = { 1,16,2,4,5,6,10,15 };
			auto res = Solution().Ans(a, b, que);
			AssertV({ {1,5},{2, 1},{1, 4},{2, 5},{1, 2},{1 ,2},{4,5}, {3 ,2 } }, res);
		}

怀疑

创建vector太多,改成double和pair也是如此。

cpp 复制代码
class Solution {
		public:	
			vector<pair<int, int>> Ans(vector<int>& a, vector<int>& b, vector<long long>& que) {
				sort(a.begin(), a.end());
				sort(b.begin(), b.end());
				vector<pair<int, int>> ans;
				for (const auto& c : que)
				{
					auto Check = [&](double mid) {
						long long cnt = 0;
						for (const auto& ib : b) {
							cnt += upper_bound(a.begin(), a.end(), mid * ib) - a.begin();
						}
						return cnt >= c;
					};
					double dAns = CBinarySearch<double>(0, 1e6, 1e-12).FindFrist(Check);
					double d1=0;
					pair<int, int> pr;
					for (const auto& ib : b) {
						auto it = upper_bound(a.begin(), a.end(), dAns * ib);
						if (a.begin() != it) {
							--it;
							if (*it / (double)ib > d1) {
								d1 = *it / (double)ib;
								pr = make_pair(*it, ib);
							}		
						}
					}				
					const int iGCD = gcd(pr.first, pr.second);
					ans.emplace_back(pr.first/iGCD, pr.second / iGCD) ;
				}
				return ans;
			}
		};

具体原因还要继续研究。

超时原因

double类型(双精度浮点数)的有效数字范围是15到16位‌。本题二分后可能mid不变,而进入死循环。换成long double就可以通过。

cpp 复制代码
class Solution {
		public:
			vector<pair<int, int>> Ans(vector<int>& a, vector<int>& b, vector<long long>& que) {
				sort(a.begin(), a.end());
				sort(b.begin(), b.end());
				vector<double> da(a.begin(), a.end());
				vector<pair<int, int>> ans;
				CBinarySearch<long double> bs(0, 1e6, 1e-12);				
				auto Check = [&](const double& mid) {
					long long cnt = 0;
					for (const auto& ib : b) {
						cnt += upper_bound(da.begin(), da.end(), mid * ib) - da.begin();
					}
					return cnt >= m_c;
				};
				for (const auto& c : que)
				{
					m_c = c;
					double dAns =bs.FindFrist(Check);
					double d1 = 0;
					pair<int, int> pr(1, 1);
					for (const auto& ib : b) {
						auto it = upper_bound(da.begin(), da.end(), dAns * ib);
						if (da.begin() != it) {
							--it;
							if (*it / (double)ib > d1) {
								d1 = *it / (double)ib;
								pr = make_pair(*it, ib);
							}
						}
					}
					const int iGCD = gcd(pr.first, pr.second);
					ans.emplace_back(pr.first / iGCD, pr.second / iGCD);
				}
				return ans;
			}
			long long m_c;
		};

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境: VS2022 C++17

如无特殊说明,本算法用**C++**实现。

相关推荐
only-qi2 小时前
leetcode2. 两数相加
算法·leetcode
鲨莎分不晴2 小时前
拯救暗淡图像:深度解析直方图均衡化(原理、公式与计算)
人工智能·算法·机器学习
DuHz3 小时前
242-267 GHz双基地超外差雷达系统:面向精密太赫兹传感与成像的65nm CMOS实现——论文阅读
论文阅读·物联网·算法·信息与通信·毫米波雷达
AI科技星3 小时前
时空的固有脉动:波动方程 ∇²L = (1/c²) ∂²L/∂t² 的第一性原理推导、诠释与验证
数据结构·人工智能·算法·机器学习·重构
阿豪只会阿巴3 小时前
【多喝热水系列】从零开始的ROS2之旅——Day4
c++·笔记·python·ros2
2401_841495644 小时前
【LeetCode刷题】寻找重复数
数据结构·python·算法·leetcode·链表·数组·重复数
罗技1234 小时前
Easysearch 集群监控实战(下):线程池、索引、查询、段合并性能指标详解
前端·javascript·算法
一路往蓝-Anbo4 小时前
C语言从句柄到对象 (七) —— 给对象加把锁:RTOS 环境下的并发安全
java·c语言·开发语言·stm32·单片机·嵌入式硬件·算法
中國龍在廣州4 小时前
谈谈2025年人工智能现状及发展趋势分析
人工智能·深度学习·算法·自然语言处理·chatgpt·机器人·机器人学习