题意:
给定序列
[
1
,
n
]
[1,n]
[1,n],用给定的合法操作猜出
x
x
x
有三种操作
- A a:问含a因子的数字的个数
- B a:问含a因子的数字的个数,之后把除了答案的所有含a因子的数字从序列删除。
- C a:确定答案是a
思路:
讲道理应该是能操着 1 0 4 10^4 104这个操作数过去的。但是本人代码太不美观了导致从1wa到了11,还有好多是因为操作数过量,希望人没事。wa了一天以后整理了一下思路如下:
正经思路: - 有一个最初的想法就是对于每个素数都先 B B B p r i m e i prime_i primei一遍,然后再 A A A p r i m e i prime_i primei看答案是不是 0 0 0,如果不是就说明含有这个素数作为因子。之后再一遍遍 A A A p r i m e i k prime_i^k primeik询问上去直到答案等于0就可。仔细思考一下, 1 0 5 10^5 105的素数数量是 9592 9592 9592,光是AB各两边遍历所有的素数就超了兄弟。
- 再想想再想想,发现其实对于每次A的询问可以分段进行。
- S t e p 1 : Step1: Step1:
-
- 每次都假设这一段里面没有我要找的 x x x,遍历段内进行 B B B p r i m e i prime_i primei的操作。
-
- 我们要做的就是维护一个 c u r cur cur(假设段内无 x x x,余下的数字总数),然后在每块的末尾进行 A A A 1 1 1的询问,看实际的答案和假定的 c u r cur cur是否相等。
-
- 如果相等就继续下一块,不相等就直接遍历块内所有素数,进行 A A A p r i m e i prime_i primei的询问,如果不等于0就必然含有这个因子。之后要做的就是 l o g n log_n logn的指数遍历就好。
- 问题又来了:咋维护啊?你也不知道除完以后去了多少个啊哥哥??要容斥定理来吗啊这
- 再想想再想想,如果我们的序列中只含有素数那是不是就问题会简单好多?每次遍历段内进行 B B B p r i m e i prime_i primei的操作之后只要进行 c u r − − cur-- cur−−就很爽啊?那要咋做到?筛鸭,不是有个啥子定理表明 N \sqrt{N} N就能把 N N N的所有的合数给整了,只剩下素数咩?那就好办了啊爹
- S t e p 2 : Step2: Step2:
-
- 把 N N N用 N \sqrt{N} N分成两部分,小的那一部分包括了我们的小素数,大的那一部分包括了大素数。
-
- 首先就是用小素数进行筛,让原数列中只含有大素数、1(也可能有 x x x)。这里小素数的个数最多大概是65(?)。在这一步中可以对于 x x x是否包含小素数进行询问试探(就是最初的想法实践一下)
-
- 然后产生的答案就可能有两种情况:
- 第一种是已经确定有小素数了,如果他是一个大素数与小素数的合数,那他必然保留在大素数序列之外,要做的就是进行 A A A p r i m e i prime_i primei的询问,看答案有没有等于 2 2 2的。
- 第二种是没有小素数,那他只可能包含大素数的一次方,因为筛过之后数列里面只剩下大素数了,就用刚刚说的,每次遍历段内进行 B B B p r i m e i prime_i primei的操作之后只要进行 c u r − − cur-- cur−−。假设大素数的个数是 G G G段内的大素数个数是 G G G,那么这样的操作我们大概需要多花费 S + G S S+\frac{G}{S} S+SG,对于 S S S的确定就很明显了,对勾函数的最低点 G \sqrt{G} G(最大好像是97?)
- 然后产生的答案就可能有两种情况:
最后大概是9800左右的操作?
以上是口胡^^
代码:
int v[maxn], prime[maxn];//v存质数,vis判断是不是质数
int primes(int n) {
int m = 0;
for (int i = 2; i <= n; i++) {
if (v[i] == 0) {//i是质数
v[i] = i; prime[++m] = i;
}
for (int j = 1; j <= m; j++) {
if (prime[j] > v[i] || prime[j] > n / i)break;
v[i*prime[j]] = prime[j];
}
}
return m;
}
int main()
{
int n, m, ans, M, g, start;
int t;
int tt, tot, temp, cur;
tot = 0;
cur = 0;
g = 0;
start = 0;
M = primes(maxn);
ans = 1;
//cout << M - tot << endl;
//cout << tot << endl;*/
sci(n);
if (n == 1) {
printf("C 1\n");
return 0;
}
for (int i = 1; prime[i] <= n; i++) {
//if (prime[i] == 99874/2)cout << 1 << endl;
cur++;
if ((LL)(prime[i]) * (LL)(prime[i]) > (LL)(n))g++;
else start = i;
}
M = cur;
//cout << (int)(sqrt(g)) << endl;
for (int i = 1; prime[i] * prime[i] <= n; i++) {//寻找小素数(可能有多个次方)
cur--;
printf("B %d\n", prime[i]);
fflush(stdout);
sci(tt);
printf("A %d\n", prime[i]);
fflush(stdout);
sci(tt);
if (tt) {
ans *= prime[i];
temp = prime[i];
while (temp*prime[i] <= n) {
temp *= prime[i];
//printf("B %d\n", temp);
printf("A %d\n", temp);
fflush(stdout);
sci(tt);
if (tt == 0)break;
else ans *= prime[i];
}
}
}
int s = (int)(sqrt(g));
int pos;
if (ans == 1) {//只含大素数
//lst = start + 1;
for (int i = 0; i <= g / s - (g%s == 0); i++) {
if (ans != 1)break;
for (int j = 0; j < s; j++) {
pos = start + i * s + j + 1;
if (pos > M)continue;
printf("B %d\n", prime[pos]);
fflush(stdout);
sci(tt);
cur--;
}
printf("A 1\n");
fflush(stdout);
sci(tt);
if (tt != cur + 1) {
for (int j = 0; j < s; j++) {
pos = start + i * s + j + 1;
if (pos > M)continue;
printf("A %d\n", prime[pos]);
fflush(stdout);
sci(tt);
if (tt) {
ans *= prime[pos];
}
}
}
}
}
else {//大素数,只可能一次方
for (int i = start + 1; prime[i] <= n; i++) {
//if (prime[i] * prime[i] <= n)continue;
printf("A %d\n", prime[i]);
fflush(stdout);
sci(tt);
if (tt == 2) {
ans *= prime[i];
break;
}
}
}
printf("C %d\n", ans);
return 0;
}