【CF1406E】Deleting Numbers(交互)
大致题意: 给定一个数\(n(n\le10^5)\),表示有一个集合其中元素为\(1\sim n\)。现你要通过两种操作在\(10000\)步以内求出未知数\(x\):A a
询问当前集合中\(a\)的倍数个数;B a
询问当前集合中\(a\)的倍数个数,然后删去所有\(a\)的倍数(如果\(x\)是\(a\)的倍数,则\(x\)将不会被删去)。
一个初步想法
考虑我们去枚举每一个质数\(p\),那么只要先\(B(p)\)删去\(p\)的倍数,再\(A(p)\)询问是否还有\(p\)的倍数,两次操作就可以判断出\(p\)是否为\(x\)的因数了。
然而,\(10^5\)以内的质数有九千多个,光这么搞一波下来就直接挂了。
根号分治
套路地去根号分治,把质因数分为小于等于\(\sqrt n\)的(称为小质数)和大于\(\sqrt n\)的(称为大质数)两类。
然后我们就可以把\(x\)分成两类:含有小质数、只含大质数。
第一类:含有小质数
小质数的数量不多,我们可以按照之前的初步想法,暴力判断出每个小质数是否为\(x\)的因数。
假设小质数中\(x\)的因数序列为\(d_1,d_2,...,d_m\)。
显然\(ans\)至少为\(\prod_{i=1}^md_i\)。
然后我们枚举\(i=1\sim m\),每次不断尝试给\(ans\)乘上\(d_i\),即如果\(x\)是\(ans\times d_i\)的倍数就更新\(ans\)。
由于更新\(ans\)最多只有\(\log n\)次,操作次数可以保证。
但此时我们只考虑了答案中小质数的部分,它还可能含有一个大质数。
这里先讲一个性质:在我们删完所有小质数的倍数之后,剩下的数恰好是\(1\)和所有的大质数(可能还有答案\(x\))。(显然)
因此,由于答案最多只含有一个大质数,我们完全可以去枚举所有大质数\(p\),如果\(A(p)=2\)(即\(p\)和\(p\times ans\)),那么答案就是\(p\times ans\)。
第二类:只含大质数
这一部分的核心就是之前已经介绍过的重要性质:在我们删完所有小质数的倍数之后,剩下的数恰好是\(1\)和所有的大质数。
再重新回头考虑一下我们的初步想法,发现它的实质就是操作每一个质数,然后每次操作完立刻检验。
那么就有一种奇妙的思路:我们可不可以不要每次操作完立刻检验,而是每隔\(S\)个数检验一次?
显然是可以的。
我们只要记一下删去\(S\)个数之后本应剩余的数的个数\(k\),如果实际个数\(A(1)=k+1\),说明答案就在这\(S\)个数之中。
然后在这\(S\)个数中再扫一遍各做一次\(A\)操作就可以找到答案了。
至于\(S\)的取值,就是要最小化\(S+\frac {G}S\),肯定是取\(\sqrt{G}\)啦。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define A(x) (printf("A %d\n",x),fflush(stdout),scanf("%d",&res),res)
#define B(x) (printf("B %d\n",x),fflush(stdout),scanf("%d",&res),res)
#define C(x) (printf("C %d\n",x),fflush(stdout))
using namespace std;
int n,s,res,Pt,P[N+5],V[N+5];
I void Sieve()//线性筛预处理质数表
{
for(RI i=2,j;i<=n;++i) for(!P[i]&&(P[++Pt]=i),
j=1;j<=Pt&&i*P[j]<=n;++j) if(P[i*P[j]]=1,!(i%P[j])) break;
}
I int Work(CI l,CI r) {for(RI i=l;i<=r;++i) if(A(P[i])) return P[i];}//在[l,r]中扫一遍求答案
int main()
{
RI i,t=0;scanf("%d",&n),s=sqrt(n),Sieve();
for(i=1;i<=Pt&&P[i]<=s;++i) B(P[i]),A(P[i])&&(V[++t]=P[i]);//求出小质数因子
RI cur=i;if(t)//如果含有小质数
{
RI ans=1;for(i=1;i<=t;++i) ans*=V[i];//先求出ans的下界
for(i=1;i<=t;++i) W(ans*V[i]<=n&&A(ans*V[i])) ans*=V[i];//不断尝试更新ans
for(i=cur;i<=Pt;++i) if(A(P[i])==2) return C(ans*P[i]),0;//找是否还有大质数因子
return C(ans),0;//找不到,直接输出
}
RI k=Pt-i+2;for(s=sqrt(k);i<=Pt;++i)//如果只含大质数
if(B(P[i]),--k,++t==s) {if(A(1)^k) return C(Work(i-s+1,i)),0;t=0;}//隔S个数检验一次
return C(A(1)^1?Work(Pt-t+1,Pt):1),0;//注意最后不完整的一段
}