• 组合数学


    之前看了一段组合数学,但是没怎么做题。最近暂时补了2题:

    POJ3252

    题目定义了一个"round number" 意思是:一个数的二进制表达中0的个数不少于1的个数,这个数就是一个round number。给你2个数Start,Finish(1 ≤ Start < Finish ≤ 2,000,000,000),求这两个数中间有多少个round number。

    由于这道题是在POJ题目分类里找的,又刚好写在“递推关系.”下面……所以一开始我还打了张表在研究有神马规律……简直囧翻天……

    不过后来也就发现这不是一个找规律,或者说递推关系式的题目……

    这两个数字太大,几乎不可能递推,除非初始化把全部的东西都先打出来,但是数字太大连1维数组都开不下。

    所以肯定是一个数学式子就差不多出来的,不会是递推式子。

    一开始想的是一个101010(随便一个x)二进制序列,要是我能把其中一位(不妨设为第k位(从左到右计数为0,1,2……))的1变成0,那么这个新的数(不妨设做y)肯定比之前的小,那么y的k位以后的数不管怎么变都会比x小;然后再在这些数中取满足条件的,把个数加起来就好了。

    而且这样做不会出现重复,因为改变第k位的时候,我保证第0~k-1位的数不动,所以当从1变成0的位置不同的时候,前缀是不一样的,一定不会重复。

    而最高位(最左边的,即k=0)一定是1,当最高位从1变成0的时候,就是求所有二进制总位数比x还小的round number的个数和了。

    用bin(x)表示 <=x 的数中有多少个round number。然后从第a个到第b个(包含a,b)就是bin(b) - bin(a-1)。

    至于求总位数(即二进制串的长度)比x小的round number的个数和的方法:不管是什么数,只要定下来了,最高位一定是1,所以把长度i定下来(0<i<len((x)2)),最高位是1,其余位数的0的个数满足不少于1的个数。即:

    (在i个位置中选(i+1)/2个位置放置0)

     

    由于写的代码实在太少,边界条件经常处理不到位。

    出现减法的时候要注意会不会出现负数。

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<string>
     4 #include<iostream>
     5 using namespace std;
     6 const int maxn = 33;
     7 int c[maxn][maxn];
     8 int num[maxn];
     9 int len[maxn];
    10 void anslen(int i)
    11 {
    12   if(i == 1){
    13     len[i] = 0;
    14     return ;
    15   }
    16   int sum = 0;
    17   //int x = i/2;
    18   int j = (i+1)/2;
    19   while(j<=i-1)
    20   {
    21     sum += c[i-1][j++];
    22   }
    23   anslen(i-1);
    24   len[i] = len[i-1]+sum;
    25 }
    26 void init()
    27 {
    28   c[0][0] = 1,c[1][0] = 1;c[1][1] = 1;
    29   memset(len,0,sizeof len);
    30   for(int i = 2;i<maxn;i++)
    31   {
    32     c[i][0] = c[i][i] = 1;
    33     for(int j = 1;j<i;j++)
    34     {
    35       c[i][j] = c[i-1][j-1]+c[i-1][j];
    36       //if(c[i][j] < 0)printf("ii:%d\n",i);
    37       //printf("%d\n",c[i][j]);
    38     }
    39   }
    40   //printf("%d\n",c[3][2]);
    41   anslen(maxn-1);
    42   //for(int i = 0;i<maxn;i++)printf("len[%d]:%d\n",i,len[i]);
    43 }
    44 int bin(int a)
    45 {
    46   memset(num,0,sizeof num);
    47   memset(count0,0,sizeof count0);
    48   int t = a,llen = 0,cnt0 = 0;
    49   while(t>0)
    50   {
    51     num[llen] = t%2;
    52     llen++;
    53     t=t/2;
    54   }
    55   int ans = len[llen-1];
    56   int tp = (llen+1)/2;
    57  // cout<<"cnt0:"<<cnt0<<endl;
    58   for(int i = llen-2;i>=0;i--){
    59     if(num[i])
    60     {
    61       int  base = tp-cnt0-1;
    62       if(base < 0)base = 0;//就是这里啊!wa到死……
    63       while(base<=i)
    64       {
    65         ans += c[i][base++];
    66        // printf("i:%d -- c[%d][%d]:%d\n",i,i,base-1,c[i][base-1]);
    67       }
    68     }
    69     else cnt0++;
    70   }
    71   if(tp <= cnt0)
    72   {
    73     ans ++;
    74   }
    75   return ans;
    76 
    77 }
    78 int main()
    79 {
    80   #ifdef LOCALL
    81   freopen("in","r",stdin);
    82   //freopen("out1","w",stdout);
    83   #endif
    84   init();
    85   int a,b;
    86 //  for(int i = 1264567;i<1264867;i++)
    87 //    cout<<bin(i-1)<<endl;
    88   while(scanf("%d%d",&a,&b)!=EOF)
    89   {
    90     int x,y;
    91     if(a == 2||a == 1) x = 0;
    92     else x = bin(a-1);
    93     if(b == 1) y = 0;
    94     else y = bin(b);
    95     printf("%d\n",y-x);
    96   }
    97 
    98   return 0;
    99 }
    POJ3252

     

    POJ1850

    http://poj.org/problem?id=1850

    组合数学很多时候为了不重复计算,都要固定一个前缀(或后缀,总之)让这个东西成为唯一性的标志。

    首先本题由于字符是有序的(相同长度的按照字典序编号,不同长度的按照长度从小到大编号),所以一旦字母确定下来,其升序一定是确定,所以一个序列就是一个组合数。

    这道题和上题其实很像,首先当给出一个字符串s时,长度len是已知的。

    首先长度比它小的字串,一定在s的前面。因为规定字母不能重复,那么就是26个字母中取0<i<len-1个。

    然后长度一样的时候,从左到右第k个字符,每次固定前k-1个,当第k个字符变成从s[k-1]+1到s[k]-1,那么第k+1个到len个的部分可以在s[k-1]+1~'z' 里面随便选(len-k-1)个。这样,到最后一个字母的时候事实上已经把所有长度为len的所有编号小于s[]的可能统计了一遍。

    这题有个坑的地方……"or 0 if the word can not be codified."……一开始没看到……wa了一次……

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<string>
     4 #include<iostream>
     5 using namespace std;
     6 const int maxn = 33;
     7 int c[maxn][maxn];
     8 void init()
     9 {
    10   c[0][0] = 1,c[1][0] = 1;c[1][1] = 1;
    11   for(int i = 2;i<maxn;i++)
    12   {
    13     c[i][0] = c[i][i] = 1;
    14     for(int j = 1;j<i;j++)
    15     {
    16       c[i][j] = c[i-1][j-1]+c[i-1][j];
    17     }
    18   }
    19 }
    20 int main()
    21 {
    22   #ifdef LOCALL
    23   //freopen("in.txt","r",stdin);
    24   //freopen("out.txt","w",stdout);
    25   #endif
    26   init();
    27   char s[22];
    28 
    29   while(scanf("%s",s)!=EOF)
    30   {
    31     int ans = 0;
    32     int len = strlen(s),flag = 0;
    33     for(int i = 1;i<len;i++)ans += c[26][i];
    34     for(int i = 0;i<len;i++){
    35       int n ,st; n = s[i]-'a';
    36       if(i){st = s[i-1]-'a'+1;
    37         if(s[i]<=s[i-1]){flag =1;break;}
    38       }
    39       else st = 0;
    40       //printf("\nn:%d\n",n);
    41       for(int j = st;j<n;j++){
    42         ans += c['z'-'a' - j][len-i-1];
    43         //printf("j:%d\tc[%d][%d]:%d\n",j,'z'-'a'-j,len-i-1, c['z'-'a' - j][len-i-1]);
    44       }
    45     }
    46     if(!flag)printf("%d\n",ans+1);
    47     else printf("0\n");
    48   }
    49 
    50   return 0;
    51 }
    POJ1850

     

    加上前几次CF和近段写题目等的总结下来,代码能力太不够了,把思路实现出来要太长的时间,还不能保证对…… 

    总之干巴爹吧!

    为什么这样是对的? 为什么那样是错的? 凭什么这样是最优的? 凭什么那样不是最优的? 为什么没有更优的解法? 就不能找一个更好的解法吗? 若不知其所以然何苦知其然?
  • 相关阅读:
    面试中要注意的 3 个 JavaScript 问题
    angularJS遇到的坑
    为什么你的前端工作经验不值钱?
    ceph pg常见的几种状态
    k8s网络之flannel
    k8s流量访问之service
    lvs+keepalived高可用
    iptables【学习笔记】
    Linux和OpenStack中的网络知识【学习笔记】
    服务器BIOS设置
  • 原文地址:https://www.cnblogs.com/PeanutPrince/p/3499264.html
Copyright © 2020-2023  润新知