light OJ 1032 连续“1”的个数(数位dp/递推)

题意:

       定义连续的两个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;

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值