ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【Coel.做题笔记】【AC300!】CF912E Prime Gift

2022-05-08 02:31:23  阅读:195  来源: 互联网

标签:Prime cnt curr Gift int sum dfs 做题 inf


题前碎语

转眼间就做了 300 道题了,所以用这道紫题充当我的 300th AC~
话不多说直接开始吧~

题目简介

洛谷传送门
CodeForces传送门
考虑到题目大意里的翻译可能有一些信息缺失,我们先把原题面看一遍。

题目描述

与 Grisha 的优异表现不同,尽管 Oleg 学了一整年,他还是没学会数论。因此,他的队友 Andrew 没有找 Ded Moroz,而是找了他,并郑重地交给他有 \(n\) 个质数的集合,以及一个简单的任务: Oleg 要找到第 \(k\) 小的整数,让这个数的所有质因数都在集合里面。

输入格式

第一行是一个整数 $ n $ ( $ 1<=n<=16 $ )。
下一行按照升序给出 \(n\) 个质数 $ p_{1},p_{2},...,p_{n} $ ( $ 2<=p_{i}<=100 $ )。
最后一行是一个整数 $ k $ ( $ 1<=k $ )。保证第 \(k\) 小的整数不超过 $ 10^{18} $。

输出格式

只有一行,为要找到的第 \(k\) 小整数,保证答案不超过 $ 10^{18}$。

解题思路

答案很大,但 \(n\) 的范围很小。我们可以考虑用深度优先搜索解决。
直接搜索当然不可能 不然这题怎么可能是紫题,因为极限情况下,组出的数字量会达到 \(10^{9}\) 数量级(可以自行枚举验证一下),考虑双向深度优先搜索(PS:这个算法在 OI-Wiki 上没有正式译名,而是直接称呼为 Meet in the middle 算法,但在深进上叫做双向深度优先搜索,为了方便就这么说了),让搜索的复杂度指数减半。
我们把素数集合分成两个部分进行搜索,把搜索到所有可能的乘积分开存放到两个数组里面。
为了后续操作方便,我们尽可能地让两个数组里存放的乘积大小相似。可以按照奇偶把素数拆成两部分,奇数下标交给一个搜索,偶数下标交给另一个。

//curr 为当前使用到的素数的下标,sum 为乘积
void dfs_first(int curr, int sum) {
    if (curr > n)
        a[++cnt_a] = sum;
    else
        for (int i = 1; inf / i >= sum; i *= p[curr]) {
        //循环条件等价于 sum * i <= inf,这样写防止溢出
            dfs_first(curr + 2, sum * i);
        }
}
void dfs_second(int curr, int sum) {
    if (curr > n)
        b[++cnt_b] = sum;
    else
        for (int i = 1; inf / i >= sum; i *= p[curr])
            dfs_second(curr + 2, sum * i);
}

将两个数组排序一下,让它们具有单调性,进行二分答案
二分的关键在于知道当前数字在所有结果里的排名,从而确定二分后得到的新区间。
那么判断的写法呼之欲出:对于当前要判断的整数 \(x\),从一个数组开头、另一个数组结尾进行遍历,寻找到 a[i] * b[j] < x 时所有可能的 j 的取值之和,也就是要找的排名。如果这一结果大于等于要找的 \(k\) ,就意味着答案在左半区间,反之为右半区间。

bool check(int x) { //true 找左边,false 找右边
    int cnt = 0;
    for (int i = 1, j = cnt_b; i <= cnt_a; i++) {
        while (j && x / a[i] < b[j]) j--;
        //同上,也是为了防溢出
        cnt += j;
    }
    return cnt >= k ? true : false;
}

完整代码如下:

#include <algorithm>
#include <iostream>

using namespace std;

#define int long long //答案很大,int 会溢出

const int maxn = 30, maxm = 6e6 + 10, inf = 1e18;

int n, k, p[maxn];
int a[maxm], cnt_a, b[maxm], cnt_b;  //存乘积的两个数组
int l = 1, r = inf, mid;

void dfs_first(int curr, int sum) {
    if (curr > n)
        a[++cnt_a] = sum;
    else
        for (int i = 1; inf / i >= sum; i *= p[curr]) {
            dfs_first(curr + 2, sum * i);
        }
}
void dfs_second(int curr, int sum) {
    if (curr > n)
        b[++cnt_b] = sum;
    else
        for (int i = 1; inf / i >= sum; i *= p[curr])
            dfs_second(curr + 2, sum * i);
}

bool check(int x) {
    int cnt = 0;
    for (int i = 1, j = cnt_b; i <= cnt_a; i++) {
        while (j && x / a[i] < b[j])
            j--;
        cnt += j;
    }
    return cnt >= k ? true : false;
}

signed main(void) {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> p[i]; //素数集合是升序的,就没必要给它排序了
    cin >> k;
    dfs_first(1, 1);
    dfs_second(2, 1);
    sort(a + 1, a + cnt_a + 1);
    sort(b + 1, b + cnt_b + 1);
    while (l < r) {
        mid = (l + r) >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    cout << r;
    return 0;
}

题后闲话

这道题还是挺难的……
又一个里程碑走过了,继续加油!

标签:Prime,cnt,curr,Gift,int,sum,dfs,做题,inf
来源: https://www.cnblogs.com/Coel-Flannette/p/16244703.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有