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 ;
}