其他分享
首页 > 其他分享> > 【Coel.做题笔记】【AC300!】CF912E Prime Gift

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

作者:互联网

题前碎语

转眼间就做了 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