本文涉及的基础知识点
[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++**实现。