洛谷题单指南-进阶搜索-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;
}

 

posted @   五月江城  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
历史上的今天:
2024-02-26 洛谷题单指南-贪心-P4447 [AHOI2018初中组] 分组
2024-02-26 洛谷题单指南-贪心-P4995 跳跳!
2024-02-26 洛谷题单指南-贪心-P1094 [NOIP2007 普及组] 纪念品分组
点击右上角即可分享
微信分享提示