CF1406E Deleting Numbers

题目来源:Codeforces, CF1406E Deleting Numbers

题目大意

题目链接

交互题。

有一个未知的整数 \(x\) (\(1\leq x\leq n\)),你需要通过交互找出它。

初始时,交互库会告诉你 \(n\)

有一个集合 \(\{1,2,\dots ,n\}\)。你可以进行不超过 \(10^4\) 次操作。每次操作可以是如下三种之一:

  • \(\text{A }a\):向交互库询问,当前集合里有多少数是 \(a\) 的倍数。
  • \(\text{B }a\):向交互库询问,当前集合里有多少数是 \(a\) 的倍数。然后将所有 \(a\) 的倍数删去。特别地:\(x\) 将不会被删去,即使它是 \(a\) 的倍数。此操作必须保证 \(a > 1\)
  • \(\text{C }a\):向交互库报告你找出的 \(x\)

请在规定操作次数内求出 \(x\)

数据范围:\(1\leq n\leq 10^5\)

本题题解

考虑一种暴力做法。枚举 \(1\dots n\) 中每个质数 \(p\),再枚举 \(p\)\(n\) 以下的所有次幂 \(p^k\)。先用 \(\text{B}\) 操作删除 \(p^k\) 的倍数,再用 \(\text{A}\) 操作查询集合里是否仍有 \(p^k\) 的倍数。若有,说明 \(x\)\(p^k\) 的倍数。这样,我们可以知道每个质数在 \(x\) 里的最大次幂是多少,根据唯一分解定理,我们也就确定了 \(x\)

\(\pi(n)\)\(1\dots n\) 中的质数数量,\(\tau(n)\)\(1\dots n\) 中的质数的幂的数量。打表发现,\(\pi(10^5)=9592,\tau(10^5)=9700\)。上述做法,需要 \(2\tau(n)+1\) 次操作(最后一次操作是报告答案),无法通过本题。

一个小优化是,对每个质数 \(p\),先用 \(\text{B}\) 操作删除所有 \(p\) 的倍数,再依次询问所有次幂。操作次数优化为 \(\pi(n)+\tau(n)+1\),但仍无法通过本题。

有一个经典的技巧是,把质数分为 \(\leq \sqrt{n}\) 的“小质数”和 \(> \sqrt{n}\) 的“大质数”。小质数总共只有 \(\pi(\sqrt{n})\) 个。而大质数在任何 \(x\) 里至多只有 \(1\) 个。

先用 \(\pi(\sqrt{n})+\tau(\sqrt{n})\leq 238\) 次操作,求出 \(x\) 中所有小质因子及其次幂。设 \(x\) 的小质因子部分为 \(x_{\text{small}}\)。此时,集合里还剩下 \(1\)\(x\)、以及所有大质数。我们要确定 \(x\) 里还剩下的那 \(1\) 个大质因子是谁(或者根本没有大质因子)。

在前面的朴素做法里,我们实际对每个大质数用了 \(2\) 次询问,这样太多了。考虑减小这个次数。分两种情况:

  • 如果 \(x_{\text{small}} > 1\):我们对每个大质数 \(p\),直接用 \(\text{A}\) 操作询问 \(p\times x_{\text{small}}\)。如果结果为 \(1\),说明 \(x\) 就等于 \(p\times x_{\text{small}}\)(不然的话,这个数此时应该已经被删了);如果结果为 \(0\),说明 \(p\) 不是 \(x\) 的大质因子。这样,对每个大质因子只需要 \(1\) 次操作。找出大质因子所需的操作次数等于大质因子的数量,为:\(\pi(n)-\pi(\sqrt{n})\)。加上小质数的部分,以及报告答案的操作,总次数为 \(\pi(\sqrt{n})+\tau(\sqrt{n})+(\pi(n)-\pi(\sqrt{n}))+1=\pi(n)+\tau(\sqrt{n})+1\leq 9766\)
  • 如果 \(x_{\text{small}}=1\):此时 \(p\times x_{\text{small}}\) 就是大质数 \(p\) 本身,它本来就应该在集合里,所以直接询问是没有意义的。这种情况下,集合里只剩 \(1\) 和大质数(因为 \(x\) 本身也是 \(1\) 或大质数)。考虑大质数序列分块。每块里,先依次用 \(\text{B}\) 操作将本块的大质数全部删除,然后用 \(\text{A}\) 操作对 \(1\) 进行一次询问(也就是询问整个集合剩余的数的数量)。如果发现集合大小减少的量,少于我们删除的量,说明 \(x\) 就在本块里,我们枚举本块中的数,依次询问。这样,设块的大小为 \(S\),则找出大质数所需的操作次数为:\(\pi(n)-\pi(\sqrt{n})+\lceil\frac{\pi(n)-\pi(\sqrt{n})}{B}\rceil+B\)。取 \(B = \lfloor\sqrt{\pi(n)-\pi(\sqrt{n})}\rfloor = 97\) 时,这部分的最大操作次数为 \(9723\)。加上小质数的部分,以及报告答案的操作,最大总次数为 \(9962\)。可以通过本题。

参考代码

// problem: CF1406E
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 1e5;
const int SQRT = 316;
const int BLOCK_SIZE = 97;

int ask(int x) {
	cout << "A " << x << endl;
	int res; cin >> res;
	return res;
}
int ask_and_del(int x) {
	cout << "B " << x << endl;
	int res; cin >> res;
	return res;
}
void report_ans(int x) {
	cout << "C " << x << endl;
}

int n, ans;
int p[MAXN + 5], cnt_p;
bool v[MAXN + 5];
void sieve(int lim) {
	for (int i = 2; i <= lim; ++i) {
		if (!v[i]) {
			p[++cnt_p] = i;
		}
		for (int j = 1; j <= cnt_p && p[j] * i <= lim; ++j) {
			v[i * p[j]] = 1;
			if (i % p[j] == 0) break;
		}
	}
}
int main() {
	cin >> n;
	sieve(n);
	ans = 1;
	// first part: 
	int second_part_start = cnt_p + 1;
	for (int i = 1; i <= cnt_p; ++i) {
		if (p[i] > SQRT) {
			second_part_start = i;
			break;
		}
		int res = ask_and_del(p[i]);
		if (!res) continue;
		int lastval = 0, flag = 0;
		for (ll j = p[i]; j <= n; j *= p[i]) {
			if (!ask(j)) {
				ans *= j / p[i];
				flag = 1;
				break;
			}
			lastval = j;
		}
		if (!flag) {
			ans *= lastval;
		}
	} // 238
	
	// second part:
	if (ans > 1) {
		for (int i = second_part_start; i <= cnt_p; ++i) {
			if ((ll)p[i] * ans > n)
				continue;
			if (ask(p[i] * ans)) {
				ans *= p[i];
				break;
			}
		} // 9765
		report_ans(ans);
	} else {
		// 分块
		for (int i = second_part_start; i <= cnt_p; i += BLOCK_SIZE) {
			int j = min(cnt_p, i + BLOCK_SIZE - 1);
			for (int k = i; k <= j; ++k) {
				ask_and_del(p[k]);
			}
			int res = ask(1);
			if (res > 1 + cnt_p - j) {
				assert(res == 1 + cnt_p - j + 1);
				for (int k = i; k <= j; ++k) {
					if (ask(p[k])) {
						ans = p[k];
						break;
					}
				}
				break;
			}
		} // 9961
		report_ans(ans);
	}
	return 0;
}
posted @ 2020-11-12 13:34  duyiblue  阅读(386)  评论(2编辑  收藏  举报