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