字典序问题
在数据加密和数据压缩中常需要对特殊的字符串进行编码。
给定的字母表A由26个小写字母组成。该字母表产生的升序字符串中字母从左到右出现的次序与字母在字母表中出现的次序相同,且每个字符最多出现1次。
例如,a,b,ab,bc,xyz等字符串都是升序字符串。
现在对字母表中产生的所有长度不超过6的升序字符串,计算它在字典中的编码。
1 | 2 | … | 26 | 27 | 28 | … |
---|---|---|---|---|---|---|
a | b | … | z | ab | ac | … |
数据输入: 输入数据油文件名为input.txt的文本文件提供。文件的第1行
是一个正整数k,表示接下来有k行。在接下来的k行中,每行给出一个字符串。
结果输出: 将计算结果输出到文件output.txt。文件有k行,每行对应一个字符串的编码。输入文件示例 输出文件示例 input.txt output.txt 2 1 a 2 b
https://blog.csdn.net/kavu1/article/details/52456135
这是一个博主写的思路分析,我认为他写的思考过程十分清晰,但代码的注释不是很详细,有点难懂,可以作为参照。
思路:
我们可以先来手动模拟一下这个过程,以cfg为例
-
首先我们要把一位,两位的升序字符串数目计算出来,从a-z,ab、ac再到bc、bd,最后到yz。
这一部分和求组合数的过程十分相似,只需求出所有组合情况,由于组合的成员是不能重复的,每种组合总会存在一种符合升序的序列,二者在数目上是相等的。
sum+=C(1,26)+C(2,26) -
再到位数和输入字符串相同,但在其之前的部分,如在cfg之前的abc,cef等。
我们需要依次计算出a开头的序列,b开头的序列,还要考虑到第二位的情况。
以a为例,后边两位即是从b开始的两位字母组合,a不会再次出现;首位为b时,后边两位就要从c开始,a、b都不会再次出现。
sum+=C(2,25)+C(2,24)
而后是从cde开始,一直到我们要求的cfg的过程。
cd和cf之间还有cd、ce
首先是cd开头的,有C(1,22),ce开头的,有C(1,21)
最后是cf开头的,只有一个cfg可选。
sum+=C(1,22)+C(1,21)
#include<stdio.h>
#include<string.h>
int combine(int m,int n){//计算组合数的函数
double sum1=1;//正常情况下,int数据类型不足以存储阶乘的位数,所以采用长整型存储中间过程
double sum2=1;//待除法运算过后,数的阶降低很多,再强制转换为int类型
if(m>n){
return 0;
}
else{
for(int i=1;i<=n-m;i++){//计算(n-m)!
sum1*=i;
}
for(int i=m+1;i<=n;i++){//计算n!/m!
sum2*=i;
}
}
return sum2/sum1;
}
//计算升序字符串编码,也就是在输入字符串之前升序字符串序列的个数,算法与组合数相同,即必须取到所有可能,但由于是升序,所以不需要排列,只需要在多种可能中取一种升序序列
int count(char str[],int low){//计算与输入字符串同位数,但在该字符串之前的升序字符串序列
int sum=0;
int len=strlen(str);
char high=str[0];
for(int i=low+1;i<=high-'a';i++){//low用于存储上一位高位到达的字母位置,便于下一位运算时,从该高位的第二位开始进行
sum+=combine(len-1,26-i);
}
if(len>1){
sum+=count(&str[1],high-'a'+1);//递归运算,将下一位作为最高位穿入,进行后边位置的计算
}
return sum;
}
int main(){
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
int n;
scanf("%d",&n);
while(n--){
int sum=1;
char str[10];
scanf("%s",str);
for(int i=1;i<=strlen(str)-1;i++){
sum+=combine(i,26);//计算在输入字符串位数之下的所有排列可能性
}
sum+=count(str,0);
printf("%d\n",sum);
}
return 0;
}