• @codeforces



    @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
    ", 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
    ", 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 学长说的一句 “双向搜索”,记了两年,现在竟然能够通过这个字眼反推出一道题的题解了。。。这是当时想都想不到的啊。。。

  • 相关阅读:
    linux常用命令全拼
    foxmail怎么设置个性签名
    linux下kerberos教程
    linux解压war包的命令
    Jenkins部署Web项目到远程tomcat(通过jenkins插件)
    Shell特殊变量:Shell $0, $#, $*, $@, $?, $$和命令行参数
    windows下命令行终端使用rz上传文件参数详解
    svn 命令行下常用的几个命令
    性能测试指标及解释
    性能测试的分类及各分类理解
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11852115.html
Copyright © 2020-2023  润新知