字典序问题
题目描述:
在数据加密和数据压缩中常需要对特殊的字符串进行编码。给定的字母表A由26个小写字母组成。该字母表产生的升序字符串中字母从左到右出现的次序与字母在字母表中出现的次序相同,且每个字符最多出现1次。例如,a,b,ab,bc,xyz等字符串都是升序字符串。现在对字母表中产生的所有长度不超过6的升序字符串,计算它在字典中的编码。
样例输入:
2
a
b
样例输出:
1
2
分析:以emx为例
从题表中可以看出,字符串的长度优先级最高,字典序次之;
因此给定一个长为len的升序字符串s,可以这么考察编码比该字符串小的字符串个数:
如上图,
①长度小于len的字符串一定比该字符串编码小√
②考察长为len的编码小于s的字符串:
※根据字典序升序排列,满足:如果字符串是s1、s2的前m位相同,第m+1位:s2[m]<s1[m] 则有:s2<s1
※而对于满足升序规则的字符串:要使s2<s1:除了s2[m]<s1[m]以外,还要满足:s2[m]>s2[m-1]
图例中emx:
①长度为1、2的升序字符串都在emx之前
②考察第一位:
Ⅰ.以a~d开头的任意长度位3的字符串√
Ⅱ.以e开头的字符串需考察第二位
考察第二位:
Ⅰ.第一位为e,以区间(e,m)之间任意字符为第二位的长为3的字符串√
Ⅱ.以m为第二位,需考察第三位
考察第三位:
Ⅰ.以em为一、二位,以区间(m,x)之间任意字符为第三位的长为3的字符串√
Ⅱ.以x为第三位,已经是最后一位,不存在字典序小于emx的字符串
每次考察新的位都相当于考察的字符串的长度减小1,而且字符串的开头被限制在一个区间内,因此可以考虑封装方法:
得到以编码为c的字符开头的长度位len的升序字符串的个数:
int getWithinNum(int c,int len){
if(len==1) return 1;//满足长度为一的以c开头的字符串只有1个
int sum=0;
for(int i=c+1;i<=26;i++)//递归减小问题规模,下一个字符比c大的升序字符串都满足条件
sum+=getWithinNum(i,len-1);
return sum;
}
封装得到所有长为len的升序字符串的个数方法:
int getSumNum(int len){
int sum=0;
for(int i=1;i<=26;i++)//任意字符开头的长为len的升序字符串个数
sum+=getWithinNum(i,len);
}
得到字典序比字符串s小的字符串个数函数代码示例:
int getFormerNum(string s){
int len=s.length();
int sum=0;
//比len短的升序字符串个数
for(int i=1;i<=len-1;i++)
sum+=getSumNum(i);
//考察每一位
int former=0,latter;
for(int i=0;i<len;i++){
latter=s[i]-'a'+1;//得到上界的编号
for(int i=former+1;i<latter;i++)
sum+=getWithinNum(i,len-i);
former=latter;//得到下一位字符的下界
}
return sum;
}
主函数:
int main(){
string s;
cin>>s;
cout<<"该字符串的编号为:"<<getFormerNum(s)+1;
return 0;
}