[二分] Codeforces 1271E Common Number
题解
容易发现,对于给定的\(N\),假设\(y\)在\(k\)个互异的\(path_j\)中出现,那么在\([1,n]\)内一定存在且只存在\(k\)个数,这\(k\)个数的二进制以\(y\)的二进制为前缀,或者若\(y\)的二进制最低位为\(0\)时,\(y\)的最低位也可进\(1\),然后再以\(y\)为前缀。
不妨令\(Judge(x)\)表示在\([1,N]\)内以\(x\)的二进制为前缀的数的数量,即出现过\(x\)的互异的\(path_j\)的数量。那么我们从\(x=1\)开始,不断地将\(x\)左移,直到\(Judge(x) < K\),即可获得\(Ans\)的二进制最大长度。然后我们在这个\(x\)的基础上,从\(x\)的二进制高位到低位不断地尝试把\(0\)变成\(1\),若改变后\(Judge(x)\)仍大于\(K\),则保持改变,否则继续判断下一位。
对于函数\(Judge(x)\),我们按在\(x\)后添加的二进制位数分开计算,假设现在我们要在\(x\)后添加一个长为\(Len\)的后缀,我们可以用二分算出长为\(Len\)的最大的后缀,即为在\(x\)后添加长度为\(Len\)的后缀时,\([1,N]\)中前缀为\(x\)的数的数量。
Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
#define RG register int
#define LL long long
LL N,K;
inline LL f(LL x,LL Len){
LL L=0,R,Res=0;
if(x&1) R=(1LL<<Len)-1;
else R=(1LL<<(Len+1))-1;
x<<=Len;
if(x>N) return 0;
while(L<=R){
LL mid=(L+R)>>1;
if(x+mid<=N){Res=mid+1;L=mid+1;}
else R=mid-1;
}
return Res;
}
bool Judge(LL x){
LL temp=x,Res=0,Len=0;
while(temp<=N){Res+=f(x,Len);++Len;temp<<=1;}
if(Res>=K) return true;
return false;
}
inline LL GetLen(LL x){
LL Len=0;
while(x){++Len;x>>=1;}
return Len-1LL;
}
int main(){
cin>>N>>K;
LL Ans=1;
while(Judge(Ans<<1)) Ans<<=1;
LL Len=GetLen(Ans);
for(RG i=Len-1;i>=0;--i)
if(Judge(Ans+(1LL<<i))) Ans+=(1LL<<i);
cout<<Ans<<endl;
return 0;
}