【CodeForces】【中途相遇法】【二分答案】912E Prime Gift

CodeForces 912E Prime Gift

題目大意

給定有NN個素數的集合(1N161\le N\le 16),要求用集合內的素數通過乘法構造出一些數字,並輸出這些數字中的第KK大。

注意一個素數可以多次使用,且答案不超過101810^{18}

分析

注意到N16N\le 16,這意味着我們可以採用爆搜。

但直接搜索似乎不太好做。考慮中途相遇法:

我們將整個集合劃分爲兩部分,對於集合中的素數爆搜出它們能夠組合出的所有數。

然後考慮答案求解。發現直接得到第KK大的數似乎有點困難,於是考慮二分。

這樣一來問題就轉化爲了給定一個數和兩個數列,求這個數在兩個序列中任選兩個數乘起來得到的序列中的排名。

這個東西用雙指針做一做就可以了。

注意不能直接將序列中的兩個數乘起來,我們必須將乘法轉化爲除法。

參考代碼

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll;
const int Maxn = 16;
const ll INF = 1e18;

int N, p[Maxn + 5];
ll K;

vector<ll> val[2];
void DFS(int typ, int x, ll now) {
	val[typ].push_back(now);
	for(int i = x; i <= N; i += 2)
		if(INF / p[i] >= now) DFS(typ, i, now * p[i]);
}

bool check(ll x) {
	ll rnk = 0;
	for(int i = (int)val[0].size() - 1, j = 0; i >= 0; i--) {
		while(j < (int)val[1].size() && val[1][j] <= x / val[0][i])
			++j;
		rnk += j;
	}
	return rnk >= K;
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	scanf("%d", &N);
	for(int i = 1; i <= N; i++)
		scanf("%d", &p[i]);
	scanf("%lld", &K);
	sort(p + 1, p + N + 1);
	DFS(0, 1, 1);
	DFS(1, 2, 1);
	sort(val[0].begin(), val[0].end());
	sort(val[1].begin(), val[1].end());
	ll lb = 1, ub = INF;
	ll ans;
	while(lb <= ub) {
		ll mid = (lb + ub) >> 1;
		if(check(mid)) ub = mid - 1, ans = mid;
		else lb = mid + 1;
	}
	printf("%lld\n", ans);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章