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位为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; }