7-14 字符串约束
分数 100
作者 imtian
单位 临沂大学
给定一个只包含下划线和大写字母的字符串,将下划线全部换成大写字母,问有多少种填法能使这个字符串满足以下三种要求:
不包含三个及以上连续的元音字母;
不包含三个及以上连续的辅音字母;
至少包含一个大写字母 L。
元音字母仅包含A,E,I,O,U,其他的字母都是辅音字母。
输入格式:
输入的一行给出一行字符串S;
∣S∣≤100
题目保证最多含有10个下划线,并且只会出现大写字母和下划线。
输出格式:
输出一个整数——表示方案数。
输入样例:
L_V
输出样例:
5
输入样例:
JA_BU_K_A
输出样例:
485
本题实质上是求满足特定条件的字符串数量,因此考虑使用枚举法暴力枚举,在检查每一种情况是否符合要求后计数。
① 题目要求把所有下划线换成大写字母,可以使用 C++ 的循环语句遍历字符串,如果遇到下划线则将其替换为对应的大写字母。
② 检查元音字母和辅音字母的连续重复情况
(1)使用 C++ 的 while 循环语句,当有 i,j,k 都存在使得 S[i] == S[j] == S[k] 时结束判断。
(2) 首先对五个元音字母和辅音字母进行枚举,分别找到所有连续出现的重复字母组合,对每个结果判断中间是否有三次或三次以上的连续出现,有则标记为不符合。值得注意的是,如果字母只能出现一次,则尝试显式写出五个字母的重复情况将涉及大量的枚举和判断,在时间上可能不够充足;如果字母可以出现多次,则可以使用桶排序进行优化,核心思想是建一个大小为 26 的数组表示每个字母的出现次数,方便通过枚举方式直接找到二次重复以上的部分。
③ 检查是否至少包含一个大写字母 L
输入的字符串中不含小写字母,因此只需要在循环遍历时判断元素是否为大写字母 L 即可。
时间复杂度:本题一共由三个部分构成,依次枚举下划线的情况和检查连续重复的情况每部分的复杂度均为 $\mathcal{O}(1)$,最后 C++ 的循环遍历复杂度为 $\mathcal{O}(n)$,因此总时间复杂度为 $\mathcal{O}(n)$。
空间复杂度:小于 $\mathcal{O}(n)$,为了判断连续重复按字典序排列的 5 个字母是否合法及桶排序的需要,考虑建立大小均为 26 的滑动窗口和数组,且是较短的常量级别操作。
#include <bits/stdc++.h>
using namespace std;
// 简单的字符映射
inline char tonum(string &s, int pos) {
if (s[pos] == '_') return 'A' + pos;
return s[pos];
}
// 挑选变动元素进行元音字母、辅音字母的枚举
int vowels[11] = {0, 1, 4, 8, 14, 20};
int consonants[17] = {2, 3, 5, 6, 7, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 21, 22};
bool for_check_correctness(string &s, int y1, int y2, int y3) {
int n = s.size();
// 替换元音字母和辅音字母为字母 A,以便进行简单的枚举
for (int i = 0; i < n; i++) {
char ch = s[i];
if (ch == 'A' || ch == 'E' || ch == 'I' || ch == 'O' || ch == 'U')
s[i] = 'A';
else
s[i] = 'B';
}
s[y1] = 'A'; s[y2] = 'A'; s[y3] = 'A'; // 用元音字母替换下划线
bool is_consecutive = false;
for (int i = 0; !is_consecutive && i < 10; i++) {
int consec = 1;
char pre = s[vowels[i]];
for (int j = 1; j < 3; j++) {
char cur = s[vowels[i + j]];
if (cur == pre)
consec++;
else
consec = 1;
if (consec >= 3) {
is_consecutive = true;
break;
}
pre = cur;
}
}
if (is_consecutive) return false;
is_consecutive = false;
for (int i = 0; !is_consecutive && i < 17; i++) {
int consec = 1;
char pre = s[consonants[i]];
for (int j = 1; j < 3; j++) {
char cur = s[consonants[i + j]];
if (cur == pre)
consec++;
else
consec = 1;
if (consec >= 3) {
is_consecutive = true;
break;
}
pre = cur;
}
}
if (is_consecutive) return false;
for (int i = 0; i < n; i++) // 恢复字符串,由 A 和 B 映射回原来的字母
if (s[i] == 'A' || s[i] == 'B')
s[i] = tonum(s, i);
return s.find('L') != string::npos; // 判断是否包含字母 L
}
int main() {
string S;
cin >> S;
int cnt = 0;
int n = S.size(), idx[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
do {
string cur = S;
for (int p = 0; p < 1024; p++) { // 暴力枚举下划线的情况
int num = p;
for (int j = 0; j < n; j++)
if (cur[j] == '_') {
cur[j] = 'A' + num % 26; // 对下划线进行某个字母的复原
num /= 26;
}
bool is_correct = false;
for (int k1 = 0; k1 < n; k1++)
for (int k2 = k1 + 1; k2 < n; k2++)
for (int k3 = k2 + 1; k3 < n; k3++)
if (cur[k1] == '_' && cur[k2] == '_' && cur[k3] == '_') {
is_correct = for_check_correctness(cur, k1, k2, k3);
if (is_correct) goto OUT; // 使用 goto 跳过当前循环
}
}
OUT:
if (is_correct) ++cnt;
} while (next_permutation(idx + 1, idx + n - 1)); // 暴力枚举下划线的排列
cout << cnt << endl;
return 0;
}