题意:
定义连续的两个1为某种数对,给出数n,将0~n中所有数转换为二进制形式,求出总共有多少个上述定义的数对。
思路:
典型的数位类dp。
1.首先预处理出以i为最高位,第i位为1/0的所有数中“一对数”的数目,包括前导零。
(这地方还是很好递推的,显然第i位为零时,f【i】【0】=f【i-1】【0】+f【i-1】【1】;当第i位为1的时候,要考虑第i-1位是否为1,如果i-1位为零,加上f【i-1】【0】,如果为1,除了加上f【i-1】【1】外,还要考虑第i位和第i-1位形成了一个数对,又因为前面的i-2个数位总共可以有(1<<(i-2)种不同形式/不同数字,所以总共多了(1<<(i-2))个数对。)
2.从从给定数字的最高位开始递推<n的数字二进制中“1数对”的数目。
如果当前位取不到最高位,后面的可以随便取,当前位取到最高位,转移到下一位上讨论最高位非最高位问题。上述其实都是套路了。关键在于当当前位前面确定的那些最高位中产生的“1数对”数目会对后面结果产生多大的影响。注意前面我们仅仅统计了比i高的位均封顶,然后第i位不取最高位时,小于i的位们所能产生的“1数对”的数目,而这些小于i的位们产生的数字最后还要拼接上大于i的那些确定封顶的位构成的数才能产生一个完整的数字。所以当i==1时(等于0时表示封顶后面不能随意取),在f【i】【0】的基础上再加上1<<(i-1)。这样做不会产生重复,因为当前考虑的时第i位不取最大限制值,递推到后面位时第i位已经取到最高位了。
AC代码:
/*Wjvje*/
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include<algorithm>
#include <set>
#include <queue>
#include <stack>
#include<vector>
#include<map>
#include<ctime>
#define LL long long
#define par pair<LL,LL>
#define INF 0x3f3f3f3f
using namespace std;
LL f[50][2];
void prework()//预处理
{
memset(f,0,sizeof(f));
for(int i=1;i<=35;++i)
{
f[i][1]=f[i-1][1]+(i==1?0:(1<<(i-2)))+f[i-1][0];
f[i][0]=f[i-1][1]+f[i-1][0];
}
}
int a[50];
LL solve(LL x)
{
memset(a,0,sizeof(a));
int tot=0;
while(x)//分解x
{
if(x&1)a[++tot]=1;
else a[++tot]=0;
x>>=1;
}
LL ans=0;
LL cnt=0;
for(int i=tot;i>=1;--i)//最高位开始递推,注意这里i下界为1,solve(x/x+1)均是;
{
if(a[i])ans+=f[i][0];//第i位不取最高位
if(a[i])ans+=cnt*(1<<(LL)(i-1));//前面“1数对的数目乘上...”
if(a[i+1]==1&&a[i]==1)cnt++;//更新当前1数对的数目
}
//ans+=cnt;//另解
/**这个是solve(x)时第一位为1的情况。
solve x+1时,求解小于的解,最后一位为1,要加上为0时的cnt,上面的循环中实现为零时,不加呗。
solve x时,最后一位为零或者一都要考虑加上,1的情况加不加要看最后一位是不是为1。**/
return ans;
}
int main()
{
int t;
scanf("%d",&t);
int cas=0;
prework();
while(t--)
{
LL x;
scanf("%lld",&x);
//printf("Case %d: %lld\n",++cas,solve(x));//另解
printf("Case %d: %lld\n",++cas,solve(x+1));
}
return 0;
}
The end;