题解:洛谷 P1098 字符串的展开
【题目来源】
洛谷:P1098 [NOIP 2007 提高组] 字符串的展开 - 洛谷(luogu.com.cn)
【题目描述】
在初赛普及组的“阅读程序写结果”的问题中,我们曾给出一个字符串展开的例子:如果在输入的字符串中,含有类似于 d-h 或者 4-8 的字串,我们就把它当作一种简写,输出时,用连续递增的字母或数字串替代其中的减号,即,将上面两个子串分别输出为 defgh 和 45678。在本题中,我们通过增加一些参数的设置,使字符串的展开更为灵活。具体约定如下:
(1)遇到下面的情况需要做字符串的展开:在输入的字符串中,出现了减号 - ,减号两侧同为小写字母或同为数字,且按照 ASCII 码的顺序,减号右边的字符严格大于左边的字符。
(2)参数 \(p_1\):展开方式。\(p_1=1\) 时,对于字母子串,填充小写字母;\(p_1=2\) 时,对于字母子串,填充大写字母。这两种情况下数字子串的填充方式相同。\(p_1=3\) 时,不论是字母子串还是数字字串,都用与要填充的字母个数相同的星号 * 来填充。
(3)参数 \(p_2\):填充字符的重复个数。\(p_2=k\) 表示同一个字符要连续填充 \(k\) 个。例如,当 \(p_2=3\) 时,子串 d-h 应扩展为 deeefffgggh。减号两边的字符不变。
(4)参数 \(p_3\):是否改为逆序:\(p_3=1\) 表示维持原来顺序,\(p_3=2\) 表示采用逆序输出,注意这时候仍然不包括减号两端的字符。例如当 \(p_1=1\)、\(p_2=2\)、\(p_3=2\) 时,子串 d-h 应扩展为 dggffeeh。
(5)如果减号右边的字符恰好是左边字符的后继,只删除中间的减号,例如:d-e 应输出为 de,3-4 应输出为 34。如果减号右边的字符按照 ASCII 码的顺序小于或等于左边字符,输出时,要保留中间的减号,例如:d-d 应输出为 d-d,3-1 应输出为 3-1。
【输入】
共两行。
第 \(1\) 行为用空格隔开的 \(3\) 个正整数,依次表示参数 \(p_1,p_2,p_3\)。
第 \(2\) 行为一行字符串,仅由数字、小写字母和减号 - 组成。行首和行末均无空格。
【输出】
共一行,为展开后的字符串。
【输入样例】
1 2 1
abcs-w1234-9s-4zz
【输出样例】
abcsttuuvvw1234556677889s-4zz
【核心思想】
-
问题分析:给定一个包含数字、小写字母和减号的字符串,根据三个参数 \(p_1, p_2, p_3\) 展开形如
a-b或1-9的子串。展开规则包括:字符类型判定(同为数字或小写字母)、填充字符大小写/星号、重复次数、正序/逆序。本质是一个字符串解析与模拟问题,需要根据规则分情况处理每个-及其两侧字符。 -
算法选择:
- 遍历 + 分类处理:遍历字符串,当遇到
-时,检查其左右字符是否满足展开条件(同为数字或同为字母,且左 < 右)。若满足,则根据 \(p_1, p_2, p_3\) 生成填充字符串;若不满足(如左 >= 右或类型不同),则原样保留-。 - 填充生成:使用循环从
left+1到right-1,根据 \(p_3\) 决定正序或逆序,根据 \(p_1\) 决定输出小写、大写或*,每个字符重复 \(p_2\) 次。
- 遍历 + 分类处理:遍历字符串,当遇到
-
关键步骤:
- 读入:读取 \(p_1, p_2, p_3\) 和字符串 \(s\)。
- 遍历:
i从 0 到s.size()-1:- 若
s[i] == '-'且i > 0且i < s.size()-1:- 左字符
L = s[i-1],右字符R = s[i+1]。 - 若
R - L == 1,则跳过(只删除减号)。 - 否则若
L >= R或!judge(L, R),则输出'-'。 - 否则调用
expand(L, R)生成填充序列。
- 左字符
- 否则输出
s[i]。
- 若
-
时间/空间复杂度:
- 时间复杂度:\(O(n \cdot p_2 \cdot |R-L|)\),但实际展开长度有限。
- 空间复杂度:\(O(1)\)。
-
字符串展开与条件分支:
- 类型判定:
isdigit和isalpha用于判断字符类型。 - 顺序与逆序:\(p_3=1\) 时正序输出,\(p_3=2\) 时逆序输出。
- 填充字符:\(p_1=1\) 小写,\(p_1=2\) 大写,\(p_1=3\) 星号。
- 边界情况:当
R - L == 1时直接跳过-,不输出填充。 - 适用场景:这类“字符串展开”问题是模拟题中较为复杂的类型,考查字符串处理和条件逻辑。
- 类型判定:
【算法标签】
普及- #模拟
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int p1, p2, p3; // p1: 控制大小写或替换为*;p2: 控制填充字符的重复次数;p3: 控制填充顺序(正序或逆序)
string s; // 输入的字符串
// 判断两个字符是否同时为数字或同时为字母
bool judge(char a, char b)
{
if (isdigit(a) && isdigit(b)) // 如果a和b都是数字
return true;
else if (isalpha(a) && isalpha(b)) // 如果a和b都是字母
return true;
return false; // 否则返回false
}
// 扩展填充函数,根据p1、p2、p3的规则填充字符
void exp(char a, char b)
{
if (p3 == 1) { // 如果p3为1,按正序填充
for (char i = a + 1; i < b; i++)
{ // 从a的下一个字符到b的前一个字符
for (int j = 0; j < p2; j++)
{ // 重复p2次
if (p1 == 1) printf("%c", tolower(i)); // p1为1,输出小写字母
else if (p1 == 2) printf("%c", toupper(i)); // p1为2,输出大写字母
else printf("*"); // p1为3,输出*
}
}
}
else
{ // 如果p3为2,按逆序填充
for (char i = b - 1; i > a; i--)
{ // 从b的前一个字符到a的下一个字符
for (int j = 0; j < p2; j++)
{ // 重复p2次
if (p1 == 1) printf("%c", tolower(i)); // p1为1,输出小写字母
else if (p1 == 2) printf("%c", toupper(i)); // p1为2,输出大写字母
else printf("*"); // p1为3,输出*
}
}
}
}
int main()
{
cin >> p1 >> p2 >> p3 >> s; // 输入p1、p2、p3和字符串s
for (int i = 0; i < s.size(); i++)
{ // 遍历字符串s
if (s[i] == '-')
{ // 如果当前字符是'-'
if (s[i + 1] - s[i - 1] == 1)
{ // 如果'-'前后的字符是连续的(如a-b)
continue; // 跳过,不处理
}
else if (s[i + 1] <= s[i - 1])
{ // 如果'-'后的字符小于等于前面的字符(如b-a或a-a)
cout << s[i]; // 直接输出'-'
continue;
}
else if (judge(s[i - 1], s[i + 1]))
{ // 如果'-'前后的字符同时为数字或字母
exp(s[i - 1], s[i + 1]); // 调用exp函数进行填充
continue;
}
}
cout << s[i]; // 输出当前字符
}
return 0;
}
【运行结果】
1 2 1
abcs-w1234-9s-4zz
abcsttuuvvw1234556677889s-4zz
浙公网安备 33010602011771号