Files
python/TangDou/AcWing/DP/ShuWei/POJ3252.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

77 lines
3.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## $POJ$ $3252$(组合数)
[题目传送门](http://poj.org/problem?id=3252)
### 一、题目描述
给出两个整数$Start,Finish$,问区间$[Start,Finish]$中 <font color='red' size=4><b>$round$ $number$</b></font>的个数。
($1<=Start<Finish<=2000000000$)
$round$ $number$定义:$RN$数即化为二进制后$0$的个数不少于$1$的个数的数。(<font color='blue' size=4><b>需要去除前导零</b></font>)
### 二、解题思路
这题的约束就是一个数的二进制中$0$的数量要不能少于$1$的数量,通过上一题,这题状态就很简单了,$dp[pos][num]$,到当前数位$pos$,$0$的数量减去$1$的数量不少于$num$的方案数,一个简单的问题,中间某个$pos$位上$num$可能为负数(这不一定是非法的,因为我还没枚举完嘛,只要最终的$num>=0$才能判合法,中途某个$pos$就不一定了),这里比较好处理,$Hash$嘛,最小就$-32$吧,直接加上$32$,把$32$当$0$用。这题主要是要想讲一下$lead$的用法,显然我要统计$0$的数量,前导零是有影响的。至于$!lead\&\&!limit$才能$dp$,都是类似的,自己慢慢体会吧。
### 三、实现代码
```c++
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
const int N = 32;
//整数范围,最大数字的二进制表示,有32位也足够
//状态范围:因为数字0的个数与数字1的个数的差,在中间过程中可能是负数,所以需要做一个偏移处理,映射到自然数的下标空间,也就是双倍空间足够
int f[N][N << 1];
int a[N];
int dfs(int pos, int st, bool lead, bool limit) {
if (pos == 0) return st >= 32; //大于等于偏移量32,也就是count(0)-count(1)>=0,此时贡献数字1个
if (!limit && !lead && ~f[pos][st]) return f[pos][st];
int up = limit ? a[pos] : 1;
int ans = 0;
for (int i = 0; i <= up; i++) {
//前一位是前导零的情况下,本位依然是0
//(1)st不变,继续传送偏移量32,也就是count(0)-count(1)=0
//(2)则需要继续传递前导零标识
if (lead && i == 0)
ans += dfs(pos - 1, st, true, limit && i == a[pos]); //有前导零就不统计在内
else
//场景1:前一位是前导零,本位不是0
//场景2:前一位不是前导零,本位是0
//场景3:前一位不是前导零,本位不是0
//此三种场景,都不需要继续传递前导零标识,即lead=false
//(1)如果本位是0,则传递st+1
//(2)如果本位是1,则传递st-1
ans += dfs(pos - 1, st + (i == 0 ? 1 : -1), false, limit && i == a[pos]);
}
if (!limit && !lead) f[pos][st] = ans;
return ans;
}
int calc(int x) {
int al = 0;
while (x) {
a[++al] = x & 1; //取出二进制的每一位表示,存入数组a中
x >>= 1;
}
// pos=al:从al位开始
// st=32:目前count(0)-count(1)=32,也就是默认加上了偏移量32
// lead=true:需要考虑前导0
// limit=true:贴上界
return dfs(al, 32, true, true);
}
int main() {
memset(f, -1, sizeof f);
// 1 ≤ Start < Finish ≤ 2,000,000,000
// INT_MAX=2147483647,看来Start和Finish还是在整数范围内
int a, b;
while (cin >> a >> b)
printf("%d\n", calc(b) - calc(a - 1));
return 0;
}
```