P1246 编码

题目传送门

一、题目解析

我们先来总结一下题意:

(1) 26个字母(a-z),最多6个

(2) 第1位可以是a-z中任何一个。

(3) 第2位只能是在第1位字符的后面字符。第3位只能是在第2位后面的字符,后面也是一样的。

(4) 这样编号后,输入一个字符串,问:它的编号是多少?如果没找到,就输出0.

二、dfs解法

看着数量不多啊,实不行就(dfs)得到所有组合,一个个加进去,然后查数应该也能(AC)~
无耻的发一轮(dfs)的代码:

#include <bits/stdc++.h>

using namespace std;
int cnt;
unordered_map<string, int> _map;
string str;
/**
 * 功能:预处理填充所有的1-6位字符串+数字映射表
 * @param len 要填充几位的字符串:1,2,3,4,5,6
 * @param pos   在指定长度的字符串前提下,当前正在填充第几位
 */
void dfs(int len, int pos) {
    //递归出口,走冒了,就停止
    if (pos > len) {
        _map[str] = ++cnt;
        return;
    }
    //默认第1位是从a开始的
    char start = 'a';
    //如果是2,3,4,5,6位,那么开始的字符应该是当前填充字符串的前面那位+1
    //为什么是k-2呢?这是因为k=1时,start='a';k=2时,需要找now[0]的值然后+1
    if (pos > 1)start = str[pos - 2] + 1;
    //从比最后一位大的字符开始,一直到z,都是可以走的路径,分别派出侦查兵进行探索
    for (char i = start; i <= 'z'; i++) {
        //填充本位置
        str[pos - 1] = i;//字符串的下标是从0开始,所以,第k个字符,下标索引是k-1
        //继续讨论下一个位置
        dfs(len, pos + 1);
    }
}

int main() {
    //预处理:不同长度的单词分别处理,len从1到6(最长6位)
    for (int len = 1; len <= 6; len++) {
        //对字符串进行清理和长度设定,这是因为后面需要向指定的位置写入字符,如果不说好长度的话就没法写入
        str.clear();
        str.resize(len);
        //开始深搜
        dfs(len, 1);
    }
    //输入
    string ask;
    cin >> ask;
    //查询输出
    cout << _map[ask] << endl;
    return 0;
}

三、组合数求法

非常经典的组合数公式题!这一题,放在第20章来讲,肯定是想让我们练习组合数的功能,上来就暴搜,太不了解出题人心理啦,差评!

那这题和组合数有个毛线关系啊?

cgx举例,设(ans)是比cgx小的单词个数,初值是(0)

分情况讨论,然后加法原理,把所有可能性加在一起。

  1. 首先,cgx 肯定比只有一个字母的单词大,(huge C_{26}^1)(ans+C_{26}^1,ans+26,ans=26)

  2. 其次,cgx 肯定比只有两个字符的单词大,

只有两个字母的单词数该怎么算呢?就是在(26)个字母中选(2)个。(huge C_{26}^2)

(ans+C_{26}^2=ans+frac{26 imes 25}{2 imes 1}=ans+325=26+325=351)

为什么是这样算呢?比如我们想一下,如果第一个字母是a,那么后面只能是b,c,d,...,z,共25个,如果第一个字母是b,那么是24个,如果第一个字母是y,那么是1个,加法原理就是(1+2+3+4+...+25=325)个!

殊途同归啊,一样的(325),这是为什么呢?仔细思考发现,(C_{26}^2)可以这样来理解,就是在(26)个字母中找出(2)个,然后肯定有一个大一个小,然后把小的在前,大的在后放上去,就是一种办法,可不对咋的。每一步一定要仔细思考啊,确定自己真正明白了才行啊~

同含义代码:

    // 1位的有C(26,1)个
    // 2位的有C(26,2)个
    // 为什么i从1到len-1呢?因为这里是找所有位数比len小的,等于len的单独讨论。
    for (int i = 1; i < len; i++)ans += C(26, i);
  1. 讨论三个字母的情况
  • 第一位:它比以字母a-b为第一位的大(cgx第一位为c,所以要比c小)

    a为第一位,且有三位的单词个数:(C_{25}^2=frac{25*24}{2*1}=300)

    从比a大的剩下(25)个字母中选2个字母:(ans+300,ans=351+300=651)

    b为第一位,且有三位的个数:(C_{24}^2=frac{24*23}{2*1}=276)

    从比b大的剩下(24)个字母中选(2)个字母:(ans+276,ans=651+276=927)

  • 第二位: 即第一位已经确定为(c)。它比以字母d-f为第二位的单词大(第一位为c,所以要比c大,第二位为g,所以要比g小)

    第二位为d的个数:(C_{22}^1=frac{22}{1}=22)

    从比d大的剩下(22)个字母中选(1)个字母: (ans+22,ans=927+22=949)

    第二位为e的个数:(C_{21}^1,ans+21,ans=949+21=970)

    第二位为f的个数:(C_{20}^1,ans+20,ans=970+20=990)

  • 第三位:即第一、二位已经确定:它比以字母h-w为第三位的大,共有(16)个。 (因为cgx的第二位是g,所以需要比g大,就是从h开始,同时,第三位是x,还得比x小,就是最大是w)。
    (w-h+1=16),共(16)个。(ans+167,ans=990+16=1006)

    因为比cgx小的单词共有(1006)个,所以cgx是第(1007)个。

根据上面的人脑模拟过程,发现要遍历每一位,即就外层循环是:

 for (int pos = 0; pos < len; pos++) {
  ...
 }

为什么(pos=0)(pos<len)结束呢?因为字符串的数组索引下标是从(0)开始到(str.size())结束的。

那循环里面需要做点什么呢?
(1)找到合适的起始字符
如果是pos=0,则起始是'a'。
如果pos>0,则起始是前一个位置的字符+1。

(2)算出合适的结束字符
结束字符,是输入字符串当前位置字符-1:也就是小于str[pos]。

(3) 分别讨论此位置是不同字符情况下的组合数

for (char j = start; j < str[pos]; j++){

}

剩余的字符个数:('z'-j)
需要挑选中的字符个数:(len - pos - 1)
组合数:(huge C_{'z'-j}^{len-pos-1})

先判断该单词是否存在,再一位一位地去考虑共有多少比给出单词小的单词,如果用的是字符串,则还需要考虑一下边界问题。

#include <bits/stdc++.h>

using namespace std;
string str;
int ans;

//求组合数的优化后办法
long long C(int n, int m) {
    long long sum = 1;
    for (int i = 1; i <= m; i++)
        sum = sum * (n - m + i) / i;
    return sum;
}

/**
 * 测试用例:cgx
 * 答案:1007
 * @return
 */
int main() {
    //输入字符串
    cin >> str;
    //字符串长度
    int len = str.size();

    //判断是否合法
    for (int i = 0; i < len; i++)
        if (str[i] <= str[i - 1]) {//非升序就是不正确的编码
            cout << 0;
            exit(0);
        }

    //1、计算出位数比它小的单词数
    //比如是cgx,那么就计算出
    // 1位的有C(26,1)个
    // 2位的有C(26,2)个
    for (int i = 1; i < len; i++)ans += C(26, i);

    //2、到这就是位数和它自己一样的了
    //  枚举每一位,从低到高,一个个来
    for (int pos = 0; pos < len; pos++) {
        //每一位都要找到比当前输入字符串小的字符串个数,叠加在一起就是结果
        char start = 'a';//初值为`a`,首位的话,从`a`开始,其它的从上一位的后面一个开始
        if (pos > 0)start = str[pos - 1] + 1; //str的数组下标是从0开始的
        for (char j = start; j < str[pos]; j++)//注意范围,找比它小的
            ans += C('z' - j, len - pos - 1); //'z' - j 剩下可用的字符数量, len-pos-1需要选择的字符数量
    }
    //别忘了最后把自己加上
    cout << ++ans;
    return 0;
}
原文地址:https://www.cnblogs.com/littlehb/p/15187778.html