这道题是一道数位dp的题目,我的方法是记忆化搜索,当然也可以预处理f [ i ] [ j ]表示 i 个空里不超过 j 个1的方案数,得出方程
f [ i ] [ j ] = f [ i - 1] [ j ] + f [ i - 1 ][ j - 1 ]
既然我用的是记忆化搜索,那就不啰嗦 f 了
记忆化搜索的核心就是从最高位开始一直搜到最低位,然后如果此状态合法,就返回1,然后在搜索的时候有两个限制,第一个是前面是否全是0,因为这样从第一个1开始才开始计算0的个数,如果冒然搞会出问题,第二个是前面的决策是否给本决策留了决策的余地,因为如果没留余地就只能照着原位来了假设原数为11000,那么你现在填了10,你下面就可以为所欲为了
具体解释代码中有
#include<iostream>
#include<cstring>
using namespace std;
int l,r;
int len=0;
int f[50][50][50];
int s[50];
int ans=0;
int dfs(int i,int x1,int x2,bool bz,bool flag)//flag表示前导0的限制,bz表示前面是否给后面
//留了余地,也就是说如果有一位原数为1,但你填了0,那么下面你就可以随便填了
{
//cout<<i<<" ";
if(!i)
{//如果到了0位
//<<"{"<<i<<" "<<x1<<" "<<x2<<" "<<bz<<" "<<flag<<"}";
if(flag==1) return 1;//如果为前导0状态,即都填0,那么也算一种状态了
if(x1>=x2) return 1;//如果当前0的位数大于等于1的位数那么就为合法状态返回1
return 0;//当前状态不合法
}
if(!bz && !flag && f[i][x1][x2]!=-1) return f[i][x1][x2];//如果没任何限制并且以前求过
int last;
if(bz) last=s[i];//如果前面有限制,那么这就得规规矩矩地来
else last=1;//如果前面没限制,那么就随便来,因为下面是0到last循环,所以这么写
int ans=0;
for(int j=0;j<=last;j++)
{//枚举本位填1还是填0
if(flag)//如果当前为前导0状态
{
if(!j)/*如果这一位还是0*/ ans+=dfs(i-1,0,0,/*如果为前导0状态那么都是0,而且最后组成的数前导0也不会算进去,也就不用关心1和0的个数了*/bz&&last==j,1);
else ans+=dfs(i-1,x1,x2+1,bz&&last==j,0);//若本位随便填,那下一位也随便填,如果本位不随便,那么看当前填的是不是根原来相等《因为是从0枚举》,若相等那么下一位得规矩了
}
else//如果不是前导0就正常来
{
if(!j)ans+=dfs(i-1,x1+1,x2,bz&&last==j,0);
else ans+=dfs(i-1,x1,x2+1,bz&&last==j,0);
}
}
if(!flag && !bz) f[i][x1][x2]=ans;//如果什么限制都没有那么可以随意用,存起来给以后用
return ans;
}
int get(int n)
{
len=0;
while(n)
{
s[++len]=n%2;
n/=2;
}
ans=dfs(len,0,0,1,1);
return ans;
}
int main()
{
cin>>l>>r;
memset(f,-1,sizeof(f));
cout<<get(r)-get(l-1);
}
//从最高位开始往后搜,搜到最后一位如果发现是合法状态就返回1