有一个素数集合,要求找到第k小的数字,满足将它分解质因数之后的所有因子都在这个素数集合里。
把这些集合中的素数分成两组,分别保存每组当中的素数互相组合相乘的结果。这里分组很有技巧性,由于2,3,5,7,11,13这前六个数组成的结果远多于其他数所组成的结果数量,所以我们取前六个为一组,后面的所有数为1组。这样,每组有不超过1e6个数。
接下来就是meet-in-the-middle了。二分答案mid,验证当前答案的大小排名,只需要找有多少个数比它小。把这个数mid分解成两组数的乘积:mid>=x*y,固定其中一个数x,在另外一个排序好的序列中查找小于等于mid/y的数的个数,再把对应所有x的满足条件的数量累加就可以得到排名。
其实还可以双指针优化,不需要每次都二分。不过这样也能卡过(3354ms)
#include <cstdio>
#include <iostream>
#include <string.h>
#include <string>
#include <unordered_map>
#include <queue>
#include <deque>
#include <vector>
#include <set>
#include <algorithm>
#include <math.h>
#include <cmath>
#include <stack>
#include <iomanip>
#define mem0(a) memset(a,0,sizeof(a))
#define meminf(a) memset(a,0x3f,sizeof(a))
using namespace std;
typedef long long ll;
typedef double db;
const int maxn=1000005,inf=0x3f3f3f3f;
const ll llinf=0x3f3f3f3f3f3f3f3f;
ll a[maxn],b[maxn],c[25];
int na = 0, nb = 0;
void dfs(int l, int r, int num, ll now) {
if (num == 0) a[++na] = now; else b[++nb] = now;
for (int i = l; i <= r; i++) {
if (c[i] <= 1000000000000000000/now) {
dfs(i, r, num, now*c[i]);
}
}
}
bool check(ll mid,ll k) {
ll sum=0;
for (int i = 1; i <= na; i++) {
if (a[i] > mid) continue;
int pos = upper_bound(b + 1, b + nb + 1, mid / a[i]) - b - 1;
sum += (ll)pos;
}
if (sum >= k) return true; else return false;
}
int main() {
ll n,k,i;
scanf("%I64d",&n);
for (i=1;i<=n;i++) {
scanf("%I64u",&c[i]);
}
scanf("%I64d",&k);
dfs(1, min(6ll, n-1), 0, 1);
dfs(min(6ll, n-1)+1, n, 1, 1);
sort(b + 1, b + nb + 1);
ll ans = 1, l, r;
l = 1; r = 1e18+5;
while (l <= r) {
ll mid = (l + r) / 2;
if (check(mid,k)) ans = mid, r = mid - 1; else l = mid + 1;
}
cout << ans << endl;
// system("pause");
return 0;
}