题目大意
如果一个数的二进制中0的数目大于等于1那么就称这个数为Round Number,问你区间[L,R]中有多少Round Number, (1≤L,R≤2∗109)
分析
问题可以转化成问从1到n有多少个Round Number
用f[i]表示 2i 之前的Round Number数目
dp[i][j] 表示长为i,0和1的数目差为j的情况数。
接下来就是状态的转移了,状态的转移稍微麻烦一点,
比如1010
然后1000~1001
最后1010~1010
大概就是这个思路,具体见代码。
一开始交了几发WA后手动测试几组小数据都对了,看代码后发现有些int应该改成long long int,改后又WA,查了一会差不出,到网上找了份AC代码对拍发现131072后的数据就不对了,这么小不会是int的问题,最后才发现是dp[i][j]数组中j开小了。
这道题折腾了我快一天了,心累。
我的方法不太优美,可以参考这篇POJ3252:Round Numbers(数位DP+记忆化DFS)
代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
#define LL long long int
LL Start,Finish;
LL dp[35][70];//dp[i][j]表示长为i,0和1的数目差为j的情况数,j为31时表示01差的数目为0
LL f[35];//f[i]表示2^i之前(不包含2^i)的Round Number数目
void Dp()
{
dp[1][32]=1;//边界
dp[1][30]=1;
for(int i=2;i<=31;i++)//总长为i的情况
for(int k=1;k<=i-1;k++)//长为k的情况
for(int j=31-i;j<=31+i;j++)//0和1差的数目
dp[i][i-k-1-1+j]+=dp[k][j];
}
void Get_f()
{
f[0]=1;//边界
LL temp_ans=0;
for(int i=1;i<=31;i++)
{
for(int j=31;j<=31+i;j++)
temp_ans+=dp[i][j];
f[i]=temp_ans;
}
}
void To2(LL n,int b[],int &lenth)//将n转换成二进制存到b数组中,下标从0开始,lenth是最终二进制长度
{
lenth=0;
while(n)
{
b[lenth++]=n%2;
n=n/2;
}
}
LL Solve(LL n)//求从0到n有多少Round number
{
if(n<=1)return 1;
LL ans=0;
int b[35];
int lenth;
To2(n,b,lenth);
int diff=-1;//到目前为止的01差
ans=f[lenth-1];
for(int loc=lenth-2;loc>=0;loc--)
{
if(loc==0)//边界
{
if(b[loc]==1 )
{
if(diff-1>=0)ans++;
if(diff+1>=0)ans++;
}
else if(b[loc]==0 && diff+1>=0)ans++;
return ans;
}
if(b[loc]==1)
{
for(int i=1;i<=loc;i++)
for(int j=31-i;j<=31+i;j++)
if(diff+j+loc+1-i>=31)
ans+=dp[i][j];
diff--;
}
else diff++;
}
return ans;
}
int main()
{
//freopen("data.in","r",stdin);
//freopen("data1.out","w",stdout);
LL ans;
Dp();
Get_f();
while(scanf("%lld%lld",&Start,&Finish)!=EOF)
{
ans=Solve(Finish)-Solve(Start-1);
printf("%lld\n",ans);
}
}