【题解】P6218 [USACO06NOV] Round Numbers S

题目传送门

这是一道数位DP

\(dp_{i,j,k}\) 为满足\(i\) 位组成,且其中有 \(j\) 个1,第 i 位(从右往左数)为 \(k\)二进制数的数量。

可以得出状态转移方程:

\(dp_{i,j,0}=dp_{i-1,j,1}+dp_{i-1,j,0}\;(2\le i,0\le j< i)\)

\(dp_{i,j,1}=dp_{i-1,j-1,0}+dp_{i-1,j-1,1}\;(2\le i,0<j\le i)\)

边界:\(dp_{1,0,0}=1,dp_{1,1,1}=1\)

对于 \(dp_{i,j,k}\),如果满足 \(1\le i,0\le j\le \lfloor \frac{i}{2}\rfloor\),则这个状态是合法的。因为0的个数为 \(i-j\),要满足 \(j\le i-j\),则 \(2j\le i\) 所以 \(j\le \lfloor \frac{i}{2}\rfloor\)

\(f(x)\) 为区间 \([1,x-1]\) 内的“圆数”个数,则区间 \([L,R]\) 内的“圆数”个数为 \(f(R+1)-f(L)\)

对于求\(f(x)\),我们先将 \(x\) 转换成二进制,设其二进制位数为 \(len\)

  1. 将二进制位数小于 \(len\) 的“圆数”个数统计到答案中。这时,对于 \(dp_{i,j,k}\),如果满足 \(1\le i,0\le j\le \lfloor \frac{i}{2}\rfloor\),则这个状态是合法的。因为0的个数为 \(i-j\),要满足 \(j\le i-j\),则 \(2j\le i\) 所以 \(j\le \lfloor \frac{i}{2}\rfloor\)

  2. 对于 \(x\) 的二进制除首位外的每一位 \(i\),都判断其是否为1 。如果为1,说明存在一些数,它们长度为 \(len\),值小于 \(x\),且二进制表示中的前 \(i-1\) 位与 \(x\) 相同,第 \(i\) 位为0 。然后将这些数中的“圆数”个数加入答案即可。这时,判断一个状态是否合法,需要考虑前 \(i-1\)位中0和1的个数。

参考代码

略微压行,轻喷。

#include <cstdio>
#include <cstring>

using namespace std;

#define in __inline__
typedef long long ll;
#define rei register int
#define FOR(i, l, r) for(rei i = l; i <= r; ++i)
#define FOL(i, r, l) for(rei i = r; i >= l; --i)
char inputbuf[1 << 23], *p1 = inputbuf, *p2 = inputbuf;
#define getchar() (p1 == p2 && (p2 = (p1 = inputbuf) + fread(inputbuf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
in int read() {
	int res = 0; char ch = getchar(); bool f = true;
	for(; ch < '0' || ch > '9'; ch = getchar())
		if(ch == '-') f = false;
	for(; ch >= '0' && ch <= '9'; ch = getchar())
		res = res * 10 + (ch ^ 48);
	return f ? res : -res;
}
const int N = 40;

ll dp[N][N][2];
int a, b, A[N], la, lb, B[N];

ll solve(int x[], int len) {
	ll res = 0; int s0 = 0, s1 = 1;
	//s0表示0的个数,s1表示1的个数
	FOL(i, len - 1, 1) FOR(j, 0, (i >> 1)) res += dp[i][j][1];//第1类数
	FOL(i, len - 1, 1) {//第二类数
		if(x[i]) FOR(j, 0, i) if(s0 + i - j >= s1 + j) res += dp[i][j][0];
		//s0+i-j表首位至当前位0的个数,s1+j表首位至当前位1的个数,注意j要枚举至i
		x[i] ? (++s1) : (++s0);
	}
	return res;
}

signed main() {
	a = read(), b = read();
	for(; a; a >>= 1) A[++la] = a & 1;
	for(; b; b >>= 1) B[++lb] = b & 1;//转换成二进制
	++B[1];
	for(rei i = 2; i <= lb && B[i - 1] == 2; ++i) B[i - 1] = 0, ++B[i];
	if(B[lb] == 2) B[lb] = 0, B[++lb] = 1;
	while(!A[la]) --la;
	while(!B[lb]) --lb;//给B加上1
	dp[1][0][0] = dp[1][1][1] = 1;
	FOR(i, 2, lb) FOR(j, 0, i) {//DP
		if(j < i) dp[i][j][0] = dp[i - 1][j][1] + dp[i - 1][j][0];
		if(j) dp[i][j][1] = dp[i - 1][j - 1][0] + dp[i - 1][j - 1][1];
	}
	printf("%lld\n", solve(B, lb) - solve(A, la));
	return 0;//结束
}

posted @ 2020-05-23 12:25  EverlastingEternity  阅读(144)  评论(0编辑  收藏  举报