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)表示iii的二进制表示中1的数量,设f[n][k]f[n][k]f[n][k]111nnnS(i)=kS(i)=kS(i)=k的数量。
因为2n=n<<12n=n<<12n=n<<1,所以有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)]f[2n][k]=f[n][k]+f[n][k1][k1==S(n)](分2n2n2n的末尾为0或1讨论)
题目即求满足f[2n][k]−f[n][k]=f[n][k−1]−[k−1==S(n)]=mf[2n][k]-f[n][k]=f[n][k-1]-[k-1==S(n)]=mf[2n][k]f[n][k]=f[n][k1][k1==S(n)]=mnnn.
比较容易看出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]是随xxx单调不降的,所以题目即求满足f[n−1][k−1]=mf[n-1][k-1]=mf[n1][k1]=mnnn的上下界,二分答案然后用数位DP检验即可。

由于此题k<=64,当m=0时n的上界为2632^{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);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值