(数位DP)【题解】CF611B-New Year and Old Property

题意

给出A、B,请你求出从 A 到 B 之间的所有数中,有多少数转化为 2 2 2 进制后,只有一个零。 1 ≤ a , b ≤ 1 0 18 1\leq a,b\leq 10^{18} 1a,b1018

思路

加上数据范围考虑就是一道很明显的数位 DP 好吧

在进行搜索的时候我们可以保存一个当前 0 0 0 的个数,搜完后判断当前 0 0 0 的个数是不是为 1 1 1 ,是的话就累加答案,最后加个记忆化就行了

code:

int dp[MAXN][MAXN];
int dfs(int pos,bool lim,int cnt0){
	if(pos==0) return (cnt0==1);
	if(!lim && ~dp[pos][cnt0]){
		return dp[pos][cnt0];
	}
	int up=lim?a[pos]:1;
	int ans=0;
	for(int i=0;i<=up;i++){
		ans+=dfs(pos-1,lim&&(i==a[pos]),cnt0+(i==0));
	}
	if(!lim){
		dp[pos][cnt0]=ans;
	}
	return ans;
}

但是这样又有问题了

在上面这份代码中,前导 0 0 0 也会被算作是 0 0 0,这样就会影响答案

所以我们还需再加一个对前导 0 0 0 的判断:

code

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
#define int long long
const int MAXN=75;
int a[MAXN];
int dp[MAXN][MAXN];
int dfs(int pos,bool lim,int cnt0,bool zero){
//	cout<<pos<<endl;
	if(pos==0) return (cnt0==1);
	if(!lim && ~dp[pos][cnt0] && !zero){
		return dp[pos][cnt0];
	}
	int up=lim?a[pos]:1;
	int ans=0;
	for(int i=0;i<=up;i++){
		ans+=dfs(pos-1,lim&&(i==a[pos]),cnt0+(i==0 && !zero),zero && i==0);
	}
	if(!lim && !zero){
		dp[pos][cnt0]=ans;
	}
	return ans;
}
int work(long long n){
	memset(dp,-1,sizeof(dp));
	int cnt=0;
	while(n){
		a[++cnt]=n%2;
		n/=2;
	}
	return dfs(cnt,1,0,1);
}
signed main(){
	long long l,r;
	scanf("%lld %lld",&l,&r);
	printf("%lld",work(r)-work(l-1));
return 0;
} 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值