我们要找给定的区间中满足下面条件的数的个数:转换为二进制,二进制中0的数量大于等于数字1的数量
那么这个题中我们要考虑前导0的存在,因为枚举二进制数位的时候,前导0都需要保证不存在,首先我们知道2^31 > 2*1e9
那么我们枚举的位数就可以确定在31位以内,状态的表示dp[i][j]:当前位置为i,在i位置之前的0\1状态为j,满足条件的数量,那么我们怎么保存01的状态呢?
开始的想法是:我只需要记录0或者1的数量就可以了,然后最后判断0的数量是不是大于等于长度的一半,但是后来发现这里存在前导0,也就是前导0不应该被记录在长度里面,后来又发现一个更好的方法,因为最长的位数是32,那么也就是最长有32个0,或者32个1,那么我们如果存在0,就在32基础上+1,出现1,在32基础上-1,那么最后只需要判断这个基准数与32的大小,如果大于等于那么就满足条件,否则不满足
做了几个数位DP之后,现在感觉对状态的表示有了一点心的认识:只要当前状态的表示能够让后面相同的情况到来时找到这个状态就行,尽可能的寻找后面能够用到相同情况的相同点
上面只是我个人的一点小见解.......
#include <iostream> #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> #include <math.h> #include <queue> #define INF 0x3f3f3f3f #define ll long long using namespace std; //0的个数大于等于1的个数,算是一个! int dp[32][66];//dp[i][j]:表示在当前位置i处,0的数量-1的数量不少于 j 的数量(因为可能小于0,那么整体向后移动32个单位) //要去除前导0的影响 int a[32]; // 2*1e9 <= 2^31 int all; int dfs(int pos, int sta,bool lead, bool limit)//去除前导0的情况 { if(pos == - 1) return sta >= 32; if(!limit && !lead && dp[pos][sta] != -1) return dp[pos][sta]; int up = limit?a[pos] :1; int ans = 0; for(int i =0 ;i <= up;i ++) { if(lead && i == 0) ans += dfs(pos-1,sta,lead,limit && i == a[pos]);//当含有前导0的时候 else ans += dfs(pos-1,sta + (i==0?1:-1),lead &&i==0,limit && i==a[pos]);//没有前导0的时候 } if(!limit && !lead) dp[pos][sta] = ans; return ans; } int Solve(int x) { int pos = 0; while(x) { a[pos ++] = x & 1; x >>= 1; } all = pos; return dfs(pos-1, 32,true, true);//0的装填是从32开始向下或者向上进行加减的 } int main() { int l,r; memset(dp,-1,sizeof(dp)); while(~scanf("%d%d",&l,&r)) { printf("%d\n",Solve(r) - Solve(l-1)); } }