【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