light oj 1032 数位DP
http://www.lightoj.com/volume_showproblem.php?problem=1032
题意:给一个整数N N (0 ≤ N < 231),问从0 到 N 所有的数的二进制数中连续两个‘1’ 出现次数的和
这种题是典型的按位DP,先预处理,然后再一位位统计过去就可以了
dp[i][0]记录长度为i二进制最高位为‘0’的所有二进制数中出现连续两个‘1’的次数之和
同样,dp[i][1]记录的是最高位为‘1’时的情况
先是预处理
for(int i=3;i<=32;i++)
{
dp[i][1]=dp[i-1][0]+dp[i-1][1]+(1<<(i-2));
dp[i][0]=dp[i-1][1]+dp[i-1][0];
}
当第i位为1,第i-1位为0时可以增加dp[i-1][0]
当第i位为1,第i-1位为1时,那么i-2位以下的数都可以随便取 ,即可以增加1<<(i-2)的相邻的1的次数,
别忘了再加上原来的dp[i-1][1]的数量
然后就是统计了,统计的时候从高位到低位一位位统计过去就行了
下面是我的两种统计方法
第二种是先统计所有的位数比N小的数的相应的和,然后再统计位数相同时的情况
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long lld;
lld dp[35][2];
void init()
{
dp[1][0]=dp[1][1]=0;
dp[2][1]=1;dp[2][0]=0;
for(int i=3;i<=32;i++)
{
dp[i][1]=dp[i-1][0]+dp[i-1][1]+(1<<(i-2));
dp[i][0]=dp[i-1][1]+dp[i-1][0];
}
}
lld calc(int num)
{
if(num<=0) return 0;
lld ans=0;
bool f=false;
int cnt=0;
int id;
for(int i=31;i>=0;i--)
{
if(num&(1<<i))
{
if(f)ans+=dp[i+1][0];
if(f==false) id=i,f=true;
ans+=(lld)cnt*(1<<i);
}
if((i+1)<=31 &&(num&(1<<(i+1))) && (num&(1<<i))) cnt++;
}
for(int i=2;i<=id;i++) ans+=dp[i][1];
ans+=cnt;
return ans;
}
int main()
{
init();
int t,ca=1,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
printf("Case %d: %lld\n",ca++,calc(n));
}
return 0;
}
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long lld;
lld dp[35][2];
void init()
{
memset(dp,0,sizeof(dp));
dp[2][1]=1;
for(int i=3;i<=32;i++)
{
dp[i][1]=dp[i-1][0]+dp[i-1][1]+(1<<(i-2));
dp[i][0]=dp[i-1][1]+dp[i-1][0];
}
}
lld calc(int num)
{
if(num<=0) return 0;
lld ans=0;
bool f=false;
int cnt=0;
for(int i=31;i>=0;i--)
{
if(num&(1<<i))
{
ans+=dp[i+1][0];
ans+=(lld)cnt*(1<<i);
//printf("cnt=%d i=%d %lld ans=%lld\n",cnt,i,dp[i][0],ans);
}
if((i+1)<=31 &&(num&(1<<(i+1))) && (num&(1<<i))) cnt++;
}
ans+=cnt;
return ans;
}
int main()
{
init();
int t,ca=1,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
printf("Case %d: %lld\n",ca++,calc(n));
}
return 0;
}