@codeforces - 912E@ Prime Gift


@description@

给定一个质数集合包含 n 个互不相同的质数,找到第 k 小的质因子全部在这个集合中的数。

Input
第一行一个整数 n (1 <= n <= 16)。
接下来一行包含 n 个不同的质数 p1, p2, ..., pn (2 <= pi <= 100),按增序给出。
接下来一个整数 k,保证最终答案 <= 10^18。

Output
输出第 k 小的满足要求的整数。保证这个数 <= 10^18。

Examples
Input
3
2 3 5
7
Output
8

Input
5
3 7 11 13 31
17
Output
93

@solution@

求第 k 小现在多得是套路。。。

考虑题目只告诉了最终答案 <= 10^18,并没有告诉 k 的大小。可以假想到这玩意儿可以很大(事实证明可以达到 7*10^8 级别)。
所以通过找第 1 小,第 2 小。。。这样依次推到第 k 小的方法不大可行。

考虑另一种方法:二分最终答案 x,求由多少满足要求的数 <= x。
怎么求呢?我们总不可能又去枚举 <= x 的所有合法的数吧,这样又绕回来了。

注意到 n 很小,考虑使用 meet in the middle。
处理出前一半的质数可以拼凑出的数的集合 S1,处理出后一半的质数可以拼凑出的数的集合 S2。最终拼凑成的数一定从 S1, S2 中各选一个相乘得到。
S1, S2 的大小?你可以打个表发现它不是很大。我也不会算,感觉跟质数啊数论啊有关的东西都挺玄学的。不过大概是 10^6 上下。

不难想到将 S1, S2 排序。二分过后,对 S1 中的每一个元素用 upper_bound 去找 S2 有多少符合要求。
但其实没有必要。注意到 S1 的 x 递增时,其对应的取值范围应该递减。所以双指针即可。

复杂度 O(10^6*log)。

@accepted code@

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 3000000;
const ll INF = ll(1E18);
int p[20], n, m; ll k;
ll v[2][MAXN + 5]; int cnt[2];
void dfs(int type, ll a, int d, int l) {
	if( d == l ) {
		v[type][cnt[type]++] = a;
		return ;
	}
	if( p[d] <= INF/a ) dfs(type, a*p[d], d, l);
	dfs(type, a, d + 1, l);
}
bool check(ll x) {
	ll ret = 0;
	int q = cnt[1] - 1;
	for(int i=0;i<cnt[0];i++) {
		if( v[0][i] > x ) break;
		ll p = x / v[0][i];
		while( q && v[1][q] > p ) q--;
		ret += q + 1;
	}
	return ret >= k;
}
int main() {
	scanf("%d", &n), m = n / 2;
	for(int i=0;i<n;i++) scanf("%d", &p[i]);
	scanf("%lld", &k);
	sort(p, p + n);
	for(int i=m;i<n;i+=2)
		swap(p[i-m], p[i]);//一点小常数优化:保证小质数与大质数均匀混合,搜出来的数可以少一点。
	dfs(0, 1, 0, m), sort(v[0], v[0] + cnt[0]);
	dfs(1, 1, m, n), sort(v[1], v[1] + cnt[1]);
//	printf("%d %d\n", cnt[0], cnt[1]);
	ll le = 1, ri = 1E18;
	while( le < ri ) {
		ll mid = (le + ri) >> 1;
		if( check(mid) ) ri = mid;
		else le = mid + 1;
	}
	printf("%lld\n", ri);
}
/*
16
2 3 5 7 11 13 17 19
23 29 31 37 41 43 47 53
*/

@details@

看到这道题的题目名字,就想起来好久好久以前的这个时候,曾经在 noip 的模拟赛中做过这道题(但是事实上没有过)。

翻看提交记录,发现已经是两年以前的事情了诶。。。
那个时候 yhn 学长还在,noip 还在,还能在机房享受做题的纯粹愉悦之情。那一场模拟赛还是 yhn 学长准备的啊。。。
当时我写了一个暴力找前 k 个的程序,现在回去看还嫌弃当时改变不完全的码风,感觉好丑啊 www。
我还记得他当时还很惊讶地看到我竟然写了这道题(虽然没过),还说 “至少用了算法” 2333,感觉也不知道是故意的还是无意间说的。

时间啊。。。时间过得真的好快。。。
而在这迅速流逝的时间中,我是否有成长呢,我是否有进步呢,我是否比过去的自己更进一步了呢。
至少从这道题看来。。。好像是这样的啊。
我仅仅记住了 yhn 学长说的一句 “双向搜索”,记了两年,现在竟然能够通过这个字眼反推出一道题的题解了。。。这是当时想都想不到的啊。。。

posted @ 2019-11-13 20:01  Tiw_Air_OAO  阅读(148)  评论(0编辑  收藏  举报