洛谷 P6218 [USACO06NOV] Round Numbers S

洛谷 P6218 [USACO06NOV] Round Numbers S

题目描述

如果一个正整数的二进制表示中,\(0\) 的数目不小于 \(1\) 的数目,那么它就被称为「圆数」。

例如,\(9\) 的二进制表示为 \(10011001\),其中有 \(2\)\(0\)\(2\)\(1\)。因此,\(9\) 是一个「圆数」。

请你计算,区间 \([l,r]\) 中有多少个「圆数」。

输入格式

一行,两个整数 \(l,r\)

输出格式

一行,一个整数,表示区间 \([l,r]\)中「圆数」的个数。
输入输出样例

输入 #1

2 12

输出 #1

6

说明/提示

【数据范围】

对于 \(100\%\) 的数据,\(1\le l,r\le 2\times 10^9\)

【样例说明】

区间 \([2,12]\) 中共有 \(6\) 个「圆数」,分别为 \(2,4,8,9,10,12\)

分析

比较套路的数位 \(DP\)

数位 \(DP\) 的实质就是换一种暴力枚举的方式,使得新的枚举方式满足 \(DP\) 的性质,然后记忆化就可以了。

首先,我们要进行 \(DP\) 的话,肯定要定义一个 \(f\) 数组存储我们计算过的值

因为这道题和数位有关,所以第一位我们要定义当前遍历到了第几位

而且我们还要判断二进制下 \(0\) 的数量和 \(1\) 的数量

所以,我们设 \(f[i][j][k]\) 为当前遍历到第 \(i\) 位,二进制下 \(1\) 的数量为 \(j\)\(0\) 的数量为 \(j\) 的数的个数

主函数我们用差分的思想搞一下即可

signed main(){
	memset(f,-1,sizeof(f));
	int l,r;
	scanf("%lld%lld",&l,&r);
	printf("%lld\n",solve(r)-solve(l-1));
	return 0;
}

然后是 \(solve\) 函数

这里的 \(cnt\) 是用来记录当前的数在二进制下有多少位,\(num\) 数组是用来记录这个数每一二进制位上的数字的

这个函数的变量只有一个 \(xx\), 返回值是 \(0\)\(xx\) 之间圆数的个数

int solve(int xx){
	memset(num,0,sizeof(num));
	cnt=0;
	while(xx){
		num[++cnt]=xx&1ll;
		xx>>=1ll;
	}
	return dfs(cnt,0,0,1,1);
}

下面的 \(dfs\) 函数是最重要的部分

int dfs(int ws,int tot1,int tot0,bool lim,bool zer){
	if(ws==0) {
		if(tot1<=tot0) return 1;
		return 0;
	}
	if(lim==0 && zer==0 && f[ws][tot1][tot0]!=-1) return f[ws][tot1][tot0];
	int up=1,ans=0;
	if(lim) up=num[ws];
	for(int i=0;i<=up;i++){
		if(zer==1 && i==0) ans+=dfs(ws-1,0,0,lim && i==up,1);
		else ans+=dfs(ws-1,tot1+(i==1),tot0+(i==0),lim && i==up,0);
	}
	if(lim==0 && zer==0)f[ws][tot1][tot0]=ans;
	return ans;
}

它的五个参数分别为:当前处理到第 \(ws\)

\(0\) 的个数 \(tot0\) ,\(1\) 的个数 \(tot1\)

\(lim\) 特判前一位是否为范围内的最大值

\(zer\) 记录有没有前导零

终止条件就是处理到最后一位

具体的边界看一下下面的模板

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
int f[60][60][60],num[55],cnt,sum[55];
const int mod=1e7+7;
int dfs(int ws,int tot1,int tot0,bool lim,bool zer){
	if(ws==0) {
		if(tot1<=tot0) return 1;
		return 0;
	}
	if(lim==0 && zer==0 && f[ws][tot1][tot0]!=-1) return f[ws][tot1][tot0];
	int up=1,ans=0;
	if(lim) up=num[ws];
	for(int i=0;i<=up;i++){
		if(zer==1 && i==0) ans+=dfs(ws-1,0,0,lim && i==up,1);
		else ans+=dfs(ws-1,tot1+(i==1),tot0+(i==0),lim && i==up,0);
	}
	if(lim==0 && zer==0)f[ws][tot1][tot0]=ans;
	return ans;
}
int solve(int xx){
	memset(num,0,sizeof(num));
	cnt=0;
	while(xx){
		num[++cnt]=xx&1ll;
		xx>>=1ll;
	}
	return dfs(cnt,0,0,1,1);
}
signed main(){
	memset(f,-1,sizeof(f));
	int l,r;
	scanf("%lld%lld",&l,&r);
	printf("%lld\n",solve(r)-solve(l-1));
	return 0;
}
posted @ 2020-08-19 17:24  liuchanglc  阅读(114)  评论(0编辑  收藏  举报