题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1032
题意:给每个数定义一个f值f(i)等于将i化为二进制后连续两个1出现的次数。给定n,求sigama(f(i))(0<=i<=n)。
思路:我们设sum(i)=sigama(f(i)),则sum(0)=sum(1)=0,sum(3)=1.sum(7)=4。这些手算一下就知道了。那么我们怎么统计数所有的二进制为全1的数字的sum值呢?我们看15=1111,[0,15]中有f值不为0的数0011,0110,0111,1011,1100,1101,1110,1111,其实0011和1011,0110和1110,0111和1111是一样的,后面数字的后三位均包含前面数字的后三位,也就是后面数字的f值至少跟前面数字的f值相同。而前面数字正好是sum(7),所以这些可以配对的数字如果我们不考虑第一个数字(比如1110和1111的第一个数字和第二个数字也能使得总数增加1),那么1011、1110和1111后三位的值也是sum(7)。那么考虑第一位数字,这时只有第二位是1时才能使答案增加1,第二位为1的数字有:1100,1101,1110,1111这些数字是12,13,14,15,也就是4+8,5+8,6+8,7+8,所以如果我们令a[i]表示[0,(2^i)-1]这个区间上的数字的f值之和的话,可得到递推公式:a[i]=2^(i-2)+a[i-1]*2;其中a[i-1]*2对应于我们上面分析的两个sum(7),2^(i-2)对应于上面的4+8,5+8,6+8,7+8这个东西。。。
接下来,就是对于给出的n怎么统计?比如n=110110,首先[000000,011111]这个区间肯定被包含了,也就是a[5],接着,从110000到110110的数字都可以跟最高位的1形成一个11使得答案增加1,然后加上这段。接着删掉高位的1得到10110,那么我们像处理110110一样递归处理10110再加上先前的是不是就是答案呢。。
#include <iostream>
#include <cstdio>
#include <cstring>
#define int64 long long
#define max(x,y) ((x)>(y)?(x):(y))
using namespace std;
int64 a[35];
int n,C,num=0;
void init()
{
a[0]=0;
a[1]=0;
a[2]=1;
a[3]=4;
int64 i,x=1;
for(i=4;i<=31;i++) a[i]=(x<<(i-2))+(a[i-1]<<1);
}
int64 get()
{
int temp=n,i;
int64 ans=0,x=1;
for(i=31;i>=1;i--) if(temp&(x<<i))
{
n^=(x<<i);
ans+=a[i]+max(0,n-(x<<(i-1))+1);
}
return ans;
}
int main()
{
init();
for(scanf("%d",&C);C--;)
{
scanf("%d",&n);
printf("Case %d: %lld\n",++num,get());
}
return 0;
}