[二分] 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;
}
原文地址:https://www.cnblogs.com/AEMShana/p/12431440.html