题目描述:
给定整数 m,k,求出正整数 n 使得 n+1,n+2,…,2n 中恰好有 m 个数 在二进制下恰好有 k 个 1。有多组数据。
输入:
第一行一个整数 t 表示数据组数。接下来 t 行每行两个整数 m,k。
输出:
每组数据输出一行两个整数,分别表示满足条件的n的最小值和最大值(无穷多此行输出一个数-1)。保证 10^18 以内存在满足条件的 n。
t<=2000,0<=m<=10^18,1<=k<=64。
题目分析:
令
S
(
i
)
S(i)
S(i)表示
i
i
i的二进制表示中1的数量,设
f
[
n
]
[
k
]
f[n][k]
f[n][k]为
1
1
1到
n
n
n中
S
(
i
)
=
k
S(i)=k
S(i)=k的数量。
因为
2
n
=
n
<
<
1
2n=n<<1
2n=n<<1,所以有
f
[
2
n
]
[
k
]
=
f
[
n
]
[
k
]
+
f
[
n
]
[
k
−
1
]
−
[
k
−
1
=
=
S
(
n
)
]
f[2n][k]=f[n][k]+f[n][k-1]-[k-1==S(n)]
f[2n][k]=f[n][k]+f[n][k−1]−[k−1==S(n)](分
2
n
2n
2n的末尾为0或1讨论)
题目即求满足
f
[
2
n
]
[
k
]
−
f
[
n
]
[
k
]
=
f
[
n
]
[
k
−
1
]
−
[
k
−
1
=
=
S
(
n
)
]
=
m
f[2n][k]-f[n][k]=f[n][k-1]-[k-1==S(n)]=m
f[2n][k]−f[n][k]=f[n][k−1]−[k−1==S(n)]=m的
n
n
n.
比较容易看出
f
[
n
]
[
k
−
1
]
−
[
k
−
1
=
=
S
(
n
)
]
=
f
[
n
−
1
]
[
k
−
1
]
f[n][k-1]-[k-1==S(n)]=f[n-1][k-1]
f[n][k−1]−[k−1==S(n)]=f[n−1][k−1].
因为 f [ x ] [ k − 1 ] f[x][k-1] f[x][k−1]是随 x x x单调不降的,所以题目即求满足 f [ n − 1 ] [ k − 1 ] = m f[n-1][k-1]=m f[n−1][k−1]=m的 n n n的上下界,二分答案然后用数位DP检验即可。
由于此题k<=64,当m=0时n的上界为 2 63 2^{63} 263,注意二分的范围。
Code:
#include<bits/stdc++.h>
#define LL unsigned long long
using namespace std;
int T,dig[65];
LL m,k,f[65][65];
LL dfs(int len,int s,bool fp){
if(!len) return s==k;
if(!fp&&~f[len][s]) return f[len][s];
LL ret=0;int mx=fp?dig[len]:1;
for(int i=0;i<=mx;i++) ret+=dfs(len-1,s+i,fp&&i==mx);
if(!fp) f[len][s]=ret;
return ret;
}
LL solve(LL n){
int len=0;
while(n) dig[++len]=n&1,n>>=1;
return dfs(len,0,1);
}
int main()
{
scanf("%d",&T);
while(T--){
memset(f,-1,sizeof f);
scanf("%lld%d",&m,&k);
if(k==1) {puts("-1");continue;}
k--;
LL l=1,r=1ull<<63,mid,L,R;
while(l<r){
mid=(l+r)>>1;
if(solve(mid-1)<m) l=mid+1;
else r=mid;
}
L=l,l=1,r=1ull<<63;
while(l<r){
mid=(l+r)>>1;
if(solve(mid-1)<=m) l=mid+1;
else r=mid;
}
R=l-1;
printf("%llu %llu\n",L,R);
}
}