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;
}