number (二分答案)

number:

10.1

思路:
令 S(i)={i+1,2,…,2i},f(i,k)表示 S(i) 中二进制下恰好有k个1的数的个数(i>0 ,k>=0 )。
f(i,k)=ΣC(ax+1,k−x+1)−C(ax,k−x+1) x=1~min(k,p),其中 p表示 i在二进制下1的个数, ax 表示i在二进制下第x高的1所在位代表的 2的幂次
不难证明 f(i,k)<=f(i+1,k);
(比较n+1~2n和n+2~2n+2,共有的部分就是n+2~2n+2,而n+1中1的个数与2n+2中1的个数是一样的,所以后者就多了一个2n+1,所以 f(i,k)是单增的)
j > 1时 f(i,k)

#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
#define ULL unsigned long long 
using namespace std;

int T , K , W[65];
LL dp[65][65] , M;

LL dfs( int len, int ned ){
    if( len == 0 ) return ned == 0;//长度为0,如果需要当前位也是0,则返回1的计数 
    LL rt = 0;
    if( W[len] == 1 ) rt += dp[len-1][ned];//若当前位是1,则当前为选0时,下一位可以随便选 
    return dfs( len - 1 , ned - W[len] ) + rt; //当前如果为1,那么选1,需要的1的个数也减一
                                                //当前如果为0,直接进入下一层 
}

LL check( ULL b ){
    memset(W, 0, sizeof( W ));
    int x = 1;
    LL rem;
    ULL tmp = b;
    while( tmp ){
        if( tmp & 1 ) W[x] = 1;
        tmp >>= 1; x++;
    }
    rem = dfs( x-1, K ) ;//记录下单倍答案 
    for( int i=x; i>0; i--)//二倍b的二进制位拆分 
        W[i] = W[i-1];
    W[0] = 0;
    return dfs( x, K ) - rem;//可用前缀和相减 求的区间值 
}

void solve(){
    LL L, R;
    ULL lf = 1Ull , rg = ( 1Ull << 63 ) - 1;
    if( K != 1 ){//K为1将会造成死循环,当K==1时,若M==1则答案为1 -1,其他M的取值均无解 
        while( lf <= rg ){//最大满足条件
            ULL mid = ( lf + rg ) >> 1;
            if( check( mid ) > M )//如果当前答案大于要求,缩小数值 
                rg = mid - 1;
            else
                R = mid, lf = mid + 1;
        }
    }
    lf = 1Ull, rg = ( 1Ull << 63 ) - 1;
    while( lf <= rg ){//最小满足条件
        ULL mid = ( lf + rg ) >> 1 ;
        if( check( mid ) < M )//如果当前答案小于要求,扩大数值 
            lf = mid + 1;
        else
            L = mid, rg = mid-1;
    }
    printf("%I64d %I64d\n", L, ( K!=1 ? R-L+1 : -1 ) ) ;
}

int main(){
    freopen("number.in", "r", stdin);
    freopen("number.out", "w", stdout);
    //dp[i][j] :: 二进制位在i位以下时,含有j个1的数字个数是多少
    for(int i=0; i<=64; i++) dp[i][0] = 1;
    for(int i=1; i<=64; i++)
        for(int j=1; j<=64; j++)
            dp[i][j] = dp[i-1][j] + dp[i-1][j-1];//当前选0加上后面的j个1,当前选1加上后面的j-1个1
    scanf("%d", &T);
    while( T-- ){
        scanf("%I64d%d", &M, &K);
        solve();
    }
    return 0 ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值