NOIP模拟赛20191017 T3 number【二分+数位DP】

题目描述:

给定整数 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][k1][k1==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][k1][k1==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][k1][k1==S(n)]=f[n1][k1].

因为 f [ x ] [ k − 1 ] f[x][k-1] f[x][k1]是随 x x x单调不降的,所以题目即求满足 f [ n − 1 ] [ k − 1 ] = m f[n-1][k-1]=m f[n1][k1]=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);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值