• 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;
    }
    
  • 相关阅读:
    zabbix+grafana使用
    其它工具网址
    IntelliJ IDEA 进行多线程调试
    mac外接显示器 双屏同时滑动问题
    wacher和acl
    zookeeper介绍
    iterm2用法与技巧
    linux下ssh公钥验证的设置和远程登录
    不变模式
    单例模式创建的三种方式
  • 原文地址:https://www.cnblogs.com/littlehb/p/15187778.html
Copyright © 2020-2023  润新知