POJ3252——Round Numbers(组合数)

题目链接

       这道题题意易理解,求给定两个数a,b之间的round numbers,所谓round numbers就是这个数转化成二进制表示后其中0的个数要大于1的个数就是round numbers,这道题开始做的时候跪了一天,但仔细想想也不是很难。


       要求出数a与数b之间的round numbers,可以先算出1到a之间的round(代指round numbers),再求出1到b之间的round,再相减即可,所以问题转移成求1到一个数n之间的round

      

       要求1到任意一个数n之间的round,我们的思路如下:

                     ①先将n转化成二进制表示,然后求出其长度L,这样比n小的round就分为长度比L小的和长度等于L的。

                     ②先求出长度比L小的round,我用自己定义的一个total函数计算的,for循环令i从1开始到L-1,依次计算total(i),在解释total()函数如何实现之前,先想一个问题,对于给定其二进制数中1的个数n和0的个数m,求其有多少种可能的情况,首先首位必须放一个1,不然就不是一个数字,那么整个数的位数就减1,也就变成(m+n-1)为,1的个数变成(n-1)位,m的个数仍旧m位,这样1和0就可以随便排列了,但要保证不重复,借助排列组合的知识,我们可以知道首先假定这些数字全都不一样,然后其全排列为(m+n-1)!,又由于它们之间1是一样的,0也是一样的,所以要除以它们内部的全排列,即最后结果为(m+n-1)!/(m!*(n-1)!),这时候,你注意到了吗?这个式子其实是从m+n-1项中选m项做组合,即C(m+n-1,m)。所以total函数的实现就是在满足0的个数大于1的个数的情况下用组合数求其所有的可能结果。

                      ③然后是求长度等于L的round,因为要求的round要比数n要小,所以我们这样思考,比如一个二进制数100010101000,我用红颜色标记的1,它的权重是比其右边所有数的权重加起来要大的,所以如果将其改成0,那么其右边的任何数改成任意值都会比原先的值小,所以这个问题又解决了,只要我们从最左边搜索1,每搜到1就可以假定其为0这样,其位数左边的值就可以随便改了,但要保证其0的总数大于1的总数。那该如何保证呢?我在代码中设置了zeros和ones分别标记从左开始已经记录的0的个数和1的个数,假设剩余的长度中有a个1和b个0,那么

                                                        zeros + ones + a + b = L ⑴

                                                        b + zeros  >=  a + ones ⑵

                           所以由可得b >= (L - 2*zeros)/2.

                         这里得到的是0的个数的下界,上界就是剩余的位置全部置0,即L - zeros - ones。但是还没完,L - 2*zeros < 0时,就会增加错误的结果,你仔细想想就会知道当前面0的数量过多的时候后面全部放1都能使这个数为round,但for循环却会从负数开始加起,这就导致结果错误,所以需要加一个判断条件当为负数时,令其为零。然后在满足round条件的情况下利用上述的组合公式求出所有符合条件的情况。

                         ④组合数求法,用的是init函数初始化C[][]的数组,C[n][m]表示从m中选n做组合。若直接用组合数公式求,中间数据必然会超出范围,那么此处的解决方法是利用杨辉三角,由杨辉三角可得到这样一个组合数公式,

                                                          C(n,m)=C(n-1,m-1)+C(n-1,m);

                          所以组合数的求法就可以用递推解决。

          这样,求得两个数的从一到该数的round,然后相减就得到结果了。


#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;

int C[35][35]={0};

void init()
{
    for(int i=0;i<=32;i++)
        for(int j=0;j<=i;j++)
            if(!j||i==j)
                C[i][j]=1;
            else
                C[i][j]=C[i-1][j-1]+C[i-1][j];
    return ;
}

int total(int l)
{
    int sum=0;
    for(int i=0;i<(l-1)/2;i++)
        sum+=C[l-1][i];
    if((l-1)%2!=0)
        sum+=C[l-1][(l-1)/2];
    return sum;
}

int Sum(int x,int flag)
{
    int y=x;
    int a[35]={0};
    int l=0;
    for(int i=0;x!=0;i++)
    {
        a[i]=x%2;
        x/=2;
        l++;
    }
    for(int i=0;i<l/2;i++)
    {
        int temp=a[i];
        a[i]=a[l-i-1];
        a[l-i-1]=temp;
    }//得到x的二进制表示
    int w=0;//w用于统计总数
    for(int i=1;i<l;i++)
        w+=total(i);//得到从1到l之间的round.
    if(flag)//如果是第二个数据,要判断其是否为round
    {
        int zeros=0, ones=0;
        for(int i=0;i<l;i++)
            if(a[i]==0)
               zeros++;
            else
                ones++;
        if(zeros>=ones)w++;
    }
    int zeros=0;
    int ones=1;
    for(int i=1;i<l;i++)//计算等长的数中比给定值小的round;
    {
        zeros++;
        if(a[i]==1)
        {
            int m=l-2*zeros;
            if(m>=0)
            {
                if(m%2!=0)
                   m=m/2+1;
                else
                   m=m/2;
            }
            else
                m=0;
            for(int j=m;j<=l-zeros-ones;j++)
                w+=C[l-zeros-ones][j];
            ones++;
            zeros--;
        }
    }
    return w;
}

int main()
{
    int a, b;
    init();
    scanf("%d%d",&a,&b);
    int x=Sum(a,0);
    int y=Sum(b,1);
    printf("%d\n",y-x);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值