【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\)最多隻有\(\log n\)次,操作次數可以保證。
但此時我們只考慮了答案中小質數的部分,它還可能含有一個大質數。
這裡先講一個性質:在我們刪完所有小質數的倍數之後,剩下的數恰好是所有的大質數(可能還有答案\(x\))。(顯然)
因此,由於答案最多隻含有一個大質數,我們完全可以去列舉所有大質數\(p\),如果\(A(p)=2\)(即\(p\)和\(p\times ans\)),那麼答案就是\(p\times ans\)。
第二類:只含大質數
這一部分的核心就是之前已經介紹過的重要性質:在我們刪完所有小質數的倍數之後,剩下的數恰好是所有的大質數。
再重新回頭考慮一下我們的初步想法,發現它的實質就是操作每一個質數,然後每次操作完立刻檢驗。
那麼就有一種奇妙的思路:我們可不可以不要每次操作完立刻檢驗,而是每隔\(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;//注意最後不完整的一段
}