《字典序最小回文构造问题》《字符串首尾相同子序列计数》《糖果均匀分配问题》

76 阅读5分钟

《字典序最小回文构造问题》

题面

image.png

问题理解

我们需要将一个给定的字符串转换为回文字符串,并且要求字典序尽可能小。在这个过程中,最多可以更改字符串中的两个字符。每个字符可以被更改为任意的小写字母。

解题思路

  1. 回文特性:回文字符串的特点是正读和反读都相同。因此,字符串的前半部分和后半部分应该是对称的。
  2. 字典序最小:为了使字典序最小,我们需要尽可能将字符改为'a'。
  3. 最多更改两个字符:我们需要在最多更改两个字符的前提下,使得字符串成为回文且字典序最小。

算法步骤

  1. 找到不对称的位置:遍历字符串的前半部分,找到所有不对称的位置(即 s[i] != s[n-i-1])。

  2. 处理不对称位置

    • 如果不对称位置的数量为2,直接将这两个位置的字符改为它们中的较小值。
    • 如果不对称位置的数量为1,需要考虑字符串的长度是否为奇数。如果是奇数,可以考虑将中间字符也改为'a'。
    • 如果不对称位置的数量为0,说明字符串已经是回文,但可能字典序不是最小。此时,我们可以尝试将前半部分中的某个字符改为'a',前提是该字符不是'a'。

具体实现

std::string solution(std::string s) {
    // write code here
    int n = s.size();
    vector<int> notSamePos;
    for(int i=0;i<n/2;i++){
        if(s[i] != s[n-i-1]){
            notSamePos.push_back(i);
        }
    }
    if(notSamePos.size() == 2){
        s[notSamePos[0]] = s[n-notSamePos[0]-1] = min(s[notSamePos[0]], s[n-notSamePos[0]-1]);
        s[notSamePos[1]] = s[n-notSamePos[1]-1] = min(s[notSamePos[1]], s[n-notSamePos[1]-1]);
    } else if(notSamePos.size() == 1){
        if(n&1){
            if(min(s[notSamePos[0]], s[n-notSamePos[0]-1]) == 'a'){
                s[notSamePos[0]] = s[n-notSamePos[0]-1] = min(s[notSamePos[0]], s[n-notSamePos[0]-1]);
                s[n/2] = 'a';
            } else {
                s[notSamePos[0]] = s[n-notSamePos[0]-1] = 'a';
            }
        } else {
            s[notSamePos[0]] = s[n-notSamePos[0]-1] = 'a';
        }
    } else {
        
        for(int i=0;i<(n+1)/2;i++){
            if(s[i] == 'a'){
                continue;
            }
            s[i] = s[n-i-1] = 'a';
            break;
        }
    }
    return s;
}

《字符串首尾相同子序列计数》

题面

image.png

问题理解

我们需要计算一个字符串中,有多少个子序列的首尾字符相同。子序列是指从原字符串中按原顺序取出若干字符(可以不连续)组成的新字符串。

数据结构选择

利用一个二维向量 pos,其中 pos[i] 存储了字符 'a' + i 在字符串中出现的所有位置。这个数据结构有助于我们快速查找某个字符的所有出现位置。

算法步骤

  1. 初始化

    • 创建一个二维向量 pos,用于存储每个字符出现的位置。
    • 初始化 ret 为字符串的长度 n,因为每个字符本身就是一个子序列。
  2. 遍历字符串

    • 对于每个字符 s[i],查找之前所有出现过的相同字符的位置 j
    • 对于每个位置 j,计算以 s[i] 为结尾、以 s[j] 为开头的子序列数量。这个数量可以通过 fastPow(2, i - j - 1) 计算,因为在这两个位置之间的字符可以选择或不选择。
    • 将计算结果累加到 ret 中,并对 998244353 取模。
  3. 更新位置

    • 将当前字符的位置 i 添加到 pos[s[i] - 'a'] 中。

具体实现

int mod = 998244353;
// 快速幂
long long fastPow(long long base, long long exp) {
    long long result = 1;
    while (exp > 0) {
        if (exp % 2 == 1) {
            result = (result * base) % mod;
        }
        base = (base * base) % mod;
        exp /= 2;
    }
    return result;
}
int solution(std::string s) {
    // write code here
    int n = s.size();
    int ret=n;
    vector<vector<int>> pos(26);
    for(int i=0;i<n;i++){
        for(int j : pos[s[i] - 'a']){
            ret = (ret + fastPow(2,i-j-1)) % mod;
        }
        pos[s[i] - 'a'].push_back(i);
    }
    return ret;
}

《糖果均匀分配问题》

题面

image.png

问题理解

题目要求我们将两种糖果分配给小朋友,使得每个小朋友至少得到一种糖果,并且每种糖果至少分给一个小朋友。目标是使得分到糖果最少的小朋友得到的糖果数尽可能多。

算法步骤

  1. 初始化结果变量:我们用 ret 来记录当前找到的最大最小糖果数。

  2. 遍历可能的分配方案

    • 我们用 i 表示分配第一种糖果的小朋友数量,n-i 表示分配第二种糖果的小朋友数量。
    • 遍历 i 由 1 到 n-1,因为每种糖果至少要分给一个小朋友。
  3. 检查分配是否可行

    • 如果 i 大于第一种糖果的数量 a,或者 n-i 大于第二种糖果的数量 b,则当前分配方案不可行,跳过。
  4. 计算当前分配方案的最小糖果数

    • 计算第一种糖果每个小朋友能分到的数量 a / i
    • 计算第二种糖果每个小朋友能分到的数量 b / (n-i)
    • 取两者的最小值作为当前分配方案的最小糖果数。
  5. 更新结果

    • 如果当前分配方案的最小糖果数大于 ret,则更新 ret
  6. 返回结果

    • 最终返回 ret,即在最优分配方案下,得到糖果最少的小朋友能得到的最大糖果数。

具体实现

int solution(int n, int a, int b) {
    // write code here
    int ret = 0;
    for(int i=1;i<=n-1;i++){
        if(n - i > b){
            continue;
        }
        if(i > a){
            break;
        }
        ret = max(ret, min(a / i, b / (n - i)));
    }
    return ret;
}