[USACO06NOV] Round Numbers S

题目链接:https://www.luogu.com.cn/problem/P6218


思路:

很明显的数位\(DP\)……

因为二进制数中 \(0/1\) 的个数有关,可以确定 \(DP\) 状态为 \(f[i][j][k]\),分别为数位、 \(0\) 的个数、\(1\)的个数。继而考虑状态转移……

讲解都在代码里了,因为面向萌新,内容较为仔细冗杂,大佬勿喷\(QAQ\)


完整代码:

#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
const int N = 40;
ll a,b,f[N][N][N];//f[i][j][k]表示共有i位,其中含有j个0和k个1的数字个数 
int tot,dig[N];
ll dfs(int len,int sum0,int sum1,bool f0,bool f1) {
	/*
	len  : 数位 
	sum0 : 0的个数
	sum1 : 1的个数
	f0   : 是否能取到最大值(1不能/0能) 
	f1   : 是否在前导零里面(1是/0不是) 
	*/ 
	if(!len) return (f1 || sum0 >= sum1);//数位为零且满足条件,返回值为1 
	if(!f1 && !f0 && f[len][sum0][sum1]) return f[len][sum0][sum1];//记忆化 
	ll res = 0;
	for(int i = 0; i <= (f0 ? dig[len] : 1); i ++) {
		if(f1 && !i) res += dfs(len - 1,0,0,i == dig[len] && f0,1);//如果位是前导零,1/0个数仍是0,继续向前找
		/*
		此处  i == dig[len] && f0 的意思是,
		除了当前位为给定数上该位的值(不能更大)而且不能取到最大值的情况,
		其他情况都能取到最大值(1不能取最大值/0能) 
		*/ 
		else {//不是前导零 
			if(!i) res += dfs(len - 1,sum0 + 1,sum1,f0 && i == dig[len],f1);
			//如果当前位为 0,(数位减1(当前数位确定),0的个数加1,1不变,同上,当前f1状态未改变)
			else res += dfs(len - 1,sum0,sum1 + 1,f0,0);//解释同上,转换一下 
		}
	}
	if(!f0 && !f1) f[len][sum0][sum1] = res;//记忆化 
	return res;
}
ll solve(ll x) {
	tot = 0;
	while(x) {//分解数位 
		dig[++tot] = x & 1;
		x >>= 1;
	}
	return dfs(tot,0,0,1,1);
	//初始状态,0/1个数均为0,当前位不能取到最大值(有限制),当前位仍是前导零(未赋值) 
}
int main() {
	scanf("%lld %lld",&a,&b);
	printf("%lld",solve(b) - solve(a - 1));
	return 0;
} 
posted @ 2021-01-16 16:19  Spring-Araki  阅读(54)  评论(0编辑  收藏  举报