组合数学

之前看了一段组合数学,但是没怎么做题。最近暂时补了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和近段写题目等的总结下来,代码能力太不够了,把思路实现出来要太长的时间,还不能保证对…… 

总之干巴爹吧!

为什么这样是对的? 为什么那样是错的? 凭什么这样是最优的? 凭什么那样不是最优的? 为什么没有更优的解法? 就不能找一个更好的解法吗? 若不知其所以然何苦知其然?
原文地址:https://www.cnblogs.com/PeanutPrince/p/3499264.html