洛谷题单指南-进阶搜索-CF912E Prime Gift
原题链接:https://www.luogu.com.cn/problem/CF912E
题意解读:给定个质数,你需要求出这些质数能乘起来组成的第小的数,注意最小的数是1。这里 的最大值为 16,且结果不超过 10^18。
解题思路:
由于的值最大为16,直接搜索会超时,我们可以考虑使用折半搜索的方法。
1、折半搜索
将给定的质数分为两组,分别生成所有可能的乘积(在10^18范围内),注意1也是乘积之一。
一种方式是前一组n/2个,后一组n-n/2个,但这样会导致两组乘积的个数分布不均,在后续的处理中可能导致超时;
要使得两组乘积个数尽可能均衡,可以将质数排序,奇数位为一组,偶数位为一组,然后对两组进行搜索所有的乘积。
//从a[k]开始枚举因子,乘积为res,v记录所有可能的乘积
void dfs(int u, LL res, vector<LL> &v)
{
if(u > n)
{
v.push_back(res);
return;
}
for(LL x = 1; res <= 1e18 / x; x *= primes[u]) //枚举因子a[k]可以对乘积贡献多少
//用除法可以防止爆longlong,注意res <= 1e18 / x不可写成x <= 1e18 / res,因为x *= primes[u]有可能溢出为负数,会导致判断错误
{
dfs(u + 2, res * x, v); //递归枚举因子a[k + 2]的贡献,每次取奇或偶位置的质数,可以使得A、B数量更加均衡
}
}
2、二分答案
对这些乘积进行排序,注意可以不需要去重,因为之前的DFS中保证不会有重复,并使用二分答案的方法来找出第小的数。
//二分答案找第k小的乘积
LL find_kth()
{
LL l = 1, r = 1e18, res = -1;
while(l <= r)
{
LL mid = (l + r) / 2;
if(check(mid) >= k) res = mid, r = mid - 1; //check(mid)表示A中数与B中数乘积<=mid的数量
else l = mid + 1;
}
return res;
}
在二分答案的过程中,我们需要一个有效的check函数来验证当前的中间值是否是第的数。这可以通过在两个已排序的乘积数组中使用双指针技巧来
实现。比如两个已排序的数组A,A,枚举A,对于A中每一个元素A[i],在B中从后往前找到第一个合适的位置B[j],使得A[i] * B[j] < mid,这样B[j]之前
的所有数和A[i]相乘都是< mid,合法数的数量累加j + 1个,如果得到合法数的个数>=K,说明二分的mid值偏大,否则偏小。
//查询A中数与B中数乘积小于等于x的数量与k的关系
bool check(LL x)
{
LL res = 0;
for(int i = 0, j = B.size() - 1; i < A.size(); i++)
{
while(j >= 0 && A[i] > x / B[j]) j--; //用除法可以防止爆longlong
res += j + 1;
}
return res >= k;
}
100分代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int n, k;
int primes[20];
vector<LL> A, B;
//从a[k]开始枚举因子,乘积为res,v记录所有可能的乘积
void dfs(int u, LL res, vector<LL> &v)
{
if(u > n)
{
v.push_back(res);
return;
}
for(LL x = 1; res <= 1e18 / x; x *= primes[u]) //枚举因子a[k]可以对乘积贡献多少
//用除法可以防止爆longlong,注意res <= 1e18 / x不可写成x <= 1e18 / res,因为x *= primes[u]有可能溢出为负数,会导致判断错误
{
dfs(u + 2, res * x, v); //递归枚举因子a[k + 2]的贡献,每次取奇或偶位置的质数,可以使得A、B数量更加均衡
}
}
//查询A中数与B中数乘积小于等于x的数量与k的关系
bool check(LL x)
{
LL res = 0;
for(int i = 0, j = B.size() - 1; i < A.size(); i++)
{
while(j >= 0 && A[i] > x / B[j]) j--; //用除法可以防止爆longlong
res += j + 1;
}
return res >= k;
}
//二分答案找第k小的乘积
LL find_kth()
{
LL l = 1, r = 1e18, res = -1;
while(l <= r)
{
LL mid = (l + r) / 2;
if(check(mid)) res = mid, r = mid - 1; //check(mid)表示A中数与B中数乘积<=mid的数量是否>=k
else l = mid + 1;
}
return res;
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++) cin >> primes[i];
cin >> k;
sort(primes + 1, primes + n + 1);
dfs(1, 1, A);
dfs(2, 1, B);
sort(A.begin(), A.end());
sort(B.begin(), B.end());
cout << find_kth();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
2024-02-26 洛谷题单指南-贪心-P4447 [AHOI2018初中组] 分组
2024-02-26 洛谷题单指南-贪心-P4995 跳跳!
2024-02-26 洛谷题单指南-贪心-P1094 [NOIP2007 普及组] 纪念品分组