字典序问题

目录

题目描述

初步分析:

状态转移方程:

代码实现:

正确性检验:


题目描述:

        在数据加密和数据压缩中常需要对特殊的字符串进行编码。给定的字母表A由26个小写字母组成。该字母表产生的升序字符串中字母从左到右出现的次序与字母在字母表中出现的次序相同,且每个字符最多出现1次。例如,a,b,ab,bc,xyz等字符串都是升序字符串。现在对字母表中产生的所有长度不超过6的升序字符串,计算它在字典中的编码。

        数据输入:输入数据由文件名为 input.txt 的文本文件提供。文件的第1行是一个正整数
 k ,表示接下来有 k 行。在接下来的 k 行中,每行给出一个字符串。
        结果输出:将计算结果输出到文件 output.txt 。文件有 k 行,每行对应一个字符串的编码。

初步分析:

        在开始设计算法之前,我们要分析要求出一个字符串的编码需要有哪些步骤,比如求字符串"bez"的顺序。首先"bez"是一个长度为3的字符串,要求它的顺序我们就要知道长度为1和长度为2的字符串的总数,然后我们要求出以a为开头、长度为3的字符串的总数,即字符串"a[][]"的数量;再是以bd开头、长度为3的字符串的总数,即字符串"bd[]"的数量;再是以bc开头、长度为3的字符串的总数,即字符串"bc[]"的数量;再是以bey开头、长度为3的字符串的总数,即字符串"bey"的数量……

        从上面我们可以看出,求解该顺序的过程其实是一个循环的过程,关键是要求出以字母first_letter为开头,长度为length的字符串的数量,即num[length][first_letter],因为上面提到的比如字符串"bc[]"的数量其实就是以c开头、长度为2的字符串的总数量。

        要求出num[length][first_letter]可以用递归实现,但是这里使用递归会导致大量的重复计算,所以本题我们用动态规划实现。

状态转移方程:

        我们建立一个二维数组用于储存以字母first_letter为开头,长度为length的字符串的数量,即num[length][first_letter]。以求num[3][a]为例,num[3][a] = num[2][b] + num[2][c] + … + num[2][z]。在实际实现中,为了方便,我们将字母映射成数字,a映射成1,b映射成2,以此类推。所以状态转移方程为:

num[len][fl] = num[len - 1][fl + 1] + num[len - 1][fl + 2] + … + num[len - 1][26]

代码实现:

        在求出num矩阵这里可以用一些小技巧,可以将复杂度降为两个循环,即添加sum和pre这两个变量,sum用于存储长度为len的字符串的总数量,计算好后会储存在num[len][0]处,这也正好将这一空闲空间利用了,而pre是储存储存长度为(len-1)且序号大于first的字符串总数,比如如果我们要求下图中的num[2][1](蓝色色块),即以a开头、长度为2的字符串数量,那么此时pre表示的就是红色色块中数字的总和。

        在求出num矩阵后,只需对输入进行映射并用循环求出最后结果即可,需要注意的是,我们定义的index表示的是目标字符串前面的字符串总数,所以要求目标字符串的顺序还需再加一。 

#include <bits/stdc++.h>
using namespace std;

int main(){
    //数据定义和初始化 
    int num[7][27];  
    //num[len][first]表示长度为len,
    //第一个字母序号为first的字符串的个数
    //num[len][0]储存长度为len的字符串的总个数 
    memset(num, 0, 7 * 27 * sizeof(int));	
    for(int i = 1; i < 27; i++)
        num[1][i] = 1;  //长度为1的字符串个数都为1
    num[1][0] = 26;     //长度为1的字符串总数为26

    //求出index矩阵 
    for(int len = 2; len < 7; len++){
        int sum = 0, pre = num[len - 1][0];  
        //sum用于储存长度为len的字符串的总数
        //pre用于储存长度为(len-1)且序号大于first的字符串总数 
        for(int first = 1; first < 27; first++){
            pre -= num[len - 1][first];
            num[len][first] += pre;	   //'='也可以 
            sum += pre;
        }
        num[len][0] = sum;              //储存sum 
    }

    //打开文件流 
    ifstream input("input.txt", ios::in);
    if(!input){
        cout << "file open error!\n";
        exit(1);
    }

    ofstream output("output.txt", ios::out | ios::trunc);
    if(!output){
        cout << "file open error!\n";
        exit(1);
    }

    //获得输出 
    int n, ch, letter, length, index;       
    //以上变量分别表示案例数量、循环界限、当前遍历字母、
    //当前遍历字符串长度、目标字符串前面的字符串总数 
    input >> n;
    for(int i = 0; i < n; i++){             //对于每个输入 
        string str;
        input >> str;
        length = (int)str.length();
        index = 0;
        ch = 0;
        for(int j = 1; j < length; j++)
            index += num[j][0];
        for(int j = length; j > 0; j--){
            int tmp = str[length - j] - 'a' + 1;		
            for(int letter = tmp - 1; letter > ch; letter--)
                index += num[j][letter];
            ch = tmp;
        }
        output << (index + 1) << '\n';	
    } 

    input.close();
    output.close();
    return 0;
}
/*
a 1
ab 27
yz 351
abc 352
bcd 652
*/

正确性检验:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值