poj 3252 数论 round numbers

以sample为例子
[2,12]区间的RoundNumbers(简称RN)个数:Rn[2,12]=Rn[0,12]-Rn[0,1]
即:Rn[start,finish]=Rn[0,finish]-Rn[0,start-1]
所以关键是给定一个X,求出Rn[0,X]
现在假设X=10100100
这个X的二进制总共是8位,任何一个小于8位的二进制都小于X
第一部分,求出长度为[0,7]区间内的二进制是RoundNumber的个数
对于一个长度为Len的二进制(最高位为1),如何求出他的RoundNumbers呢(假设为用R(len)来表达),分为奇数和偶数两种情况
1、奇数情况:在Len=2k+1的情况下,最高位为1,剩下2k位,至少需要k+1为0
用C(m,n)表示排列组合数:从m个位置选出n个位置的方法
R(len)=C(2k,k+1)+C(2k,k+2)+...+C(2k,2k).
由于 A:C(2k,0)+C(2k,1)+...+C(2k,2k)=2^(2k)
B:C(2k,0)=C(2k,2k), C(2k,1)=C(2k,2k-1) ,,C(2k,i)=C(2k,2k-i)
于是 C(2k,0)+C(2k,1)+...+C(2k,2k)
= C(2k,0)+C(2k,1)+...+C(2k,k)+C(2k,k+1)+C(2k,K+2)+...+C(2k,2k)
= 2*R(len)+C(2k,k)
=2^(2k)
所以R(len)=1/2*{2^(2k)-C(2k,k)};
2. 偶数情况 len=2*k,类似可以推到 R(len)=1/2*(2^(2k-1));
第二部分,对于上面这个长度为8的例子:即X=10100100,首先如果本身是RoundNumbers,第二部分的结果总数+1
第一部分已经将长度小于8的部分求出。现在要求长度=8的RoundNumber数目
长度为8,所以第一个1不可改变
现在到第二个1,如果Y是前缀如100*****的二进制,这个前缀下,后面取0和1必然小于X,已经有2个0,一个1,剩下的5个数字中至少需要2个0,
所以把第二个1改为0:可以有C(5,2)+C(5,3)+C(5,4)+C(5,5)
现在第三个1,也就是前最为101000**,同样求出,至少需要0个0就可,所以有C(2,0)+C(2,1)+C(2,2)个RoundNumbers
。。。
将所有除了第一个1以外的1全部变为0,如上算出有多少个RoundNumbers,结果相加(由于前缀不一样,所以后面不管怎么组合都是唯一的)

将第一部分和第二部分的结果相加,就是最后的结果了。
精度要求方面,用int就可以了:two billion=20亿<2*1024*1024*1024=2^31,需用31位来表示数组,由于第一位总是1,所以求组合数的时候最多求30,C(30,k),k取值区间是[0,30],因为C(k,i)<2^k,所以结果用int表示就可以

本文还是开成__int64位。



#include <stdio.h>
#include <iostream>
#include <cmath>
using namespace std;
int Arr[32];
bool flag[32],isOK;
//计算组合m组中的n组
__int64 C(int m,int n)
{
    __int64 r=1;
    int h=(m-n)<n?(m-n):n;
    for(int i=1;i<=h;i++)
    {
        r*=m;
        r/=i;
        m--;
    }
    return r;
}
/*
make_list(){
    for(int i=0;i<33;i++){  
        c[i][0]=c[i][i]=1;  
        for(int j=1;j<i;j++)  
            c[i][j]=c[i-1][j]+c[i-1][j-1];  
    }     
            
}
*/

int Len(int n)
{
    return (int)(log(n*1.0)/log(2.0))+1;
}

int make_list(){
    Arr[1]=0;
    for(int i=2;i<=31;i++)
        for(int k=(i-1)/2+1;k<=i-1;k++)
            Arr[i]+=C(i-1,k);        
  return 0;          
}


__int64 math(int n){
   if(n==0)
        return 0;
    int zero=0,one=1,rightzero,len=Len(n);
    __int64 cnt=0;
    memset(flag,0,sizeof(flag));
    //累加Len(n)之前的和
    for(int i=1;i<len;i++)
        cnt+=Arr[i];
    //确定n的那些位为1
    for(int i=0;i<len;i++)
    {
        if((n>>i) & 1)
            flag[i+1]=true;
        else
            flag[i+1]=false;
    }
    for(int i=len-1;i>0;i--)
    {        
        //n为偶数
        if(len%2==0)
        {
            rightzero=len/2;
            if(flag[i])
            {
                ++one;
                  for(int k=rightzero-zero-1;k<i;k++)
                  {
                      if(k>=0)
                       cnt+=C(i-1,k);
                  }
            }
            else
                ++zero;

        }
        //n为奇数 
        else
        {
            rightzero=(len+1)/2;
            if(flag[i])
            {
                ++one;
                 for(int k=rightzero-zero-1;k<i;k++)
                  {
                      if(k>=0)
                       cnt+=C(i-1,k);
                  }
            }
            else
                ++zero;
        }
    }    
    zero=0;one=0;
    //判断n自身是不是Round Numbers
    for(int i=1;i<=len;i++)
    {
        if(flag[i])
            one++;
        else
            zero++;
    }
    if(zero>=one)
        cnt++;
    return  cnt;        
         
}
int main(){
    int sta, fin;
    make_list();
    cin >> sta >> fin;
    cout << math(fin) - math(sta - 1) << endl;//  这里记得是sta- 1。
    system("PAUSE");
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值