题目如下:
题目 字符串的展开
在初赛普及组的“阅读程序写结果”的问题中,我们曾给出一个字符串展开的例子:如果在输入的字符串中,含有类似于“d-h”或“4-8”的子串,我们就把它当作一种简写,输出时,用连续递增的字母或数字串替代其中的减号,即,将上面两个子串分别输出为“defgh”和“45678”。在本题中,我们通过增加一些参数的设置,使字符串的展开更为灵活。具体约定如下:
(1)遇到下面的情况需要做字符串的展开:在输入的字符串中,出现了减号“-”,减号两侧同为小写字母或同为数字,且按照ASCII码的顺序,减号右边的字符严格大于左边的字符。
(2)参数 p1:展开方式。p1=1 时,对于字母子串,填充小写字母;p1=2 时,对于字母子串,填充大写字母。这两种情况下数字子串的填充方式相同。p1=3 时,不论是字母子串还是数字子串,都用与要填充的字母个数相同的星号“*”来填充。
(3)参数 p2:填充字符的重复个数。p2=k 表示同一个字符要连续填充 k 个。例如,当 p2=3 时,子串“d-h”应扩展为“deeefffgggh”。减号两侧的字符不变。
(4)参数 p3:是否改为逆序:p3=1 表示维持原有顺序,p3=2 表示采用逆序输出,注意这时仍然不包括减号两端的字符。例如当 p1=1、p2=2、p3=2 时,子串“d-h”应扩展为“dggffeeh”。
(5)如果减号右边的字符恰好是左边字符的后继,只删除中间的减号,例如:“d-e”应输出为“de”,“3-4”应输出为“34”。如果减号右边的字符按照ASCII码的顺序小于或等于左边字符,输出时,要保留中间的减号,例如:“d-d”应输出为“d-d”,“3-1”应输出为“3-1”。
输入
第 1 行为用空格隔开的 3 个正整数,依次表示参数 p1,p2,p3。 第 2 行为一行字符串,仅由数字、小写字母和减号“-”组成。行首和行末均无空格。
输出
输出一行,为展开后的字符串。
本题题干条件相当长,而且信息量非常大,多个参数的设置和多种展开需要识别的序列情况导致了多种选择结构,如果直接上手写代码,很可能会把自己写绕进去(比如我)。遇到这种字符串模拟题,应当先确认复杂选择结构的思路,一种可行的思路如图:
这是符合程序选择结构的流程图,如此复杂如果直接上手去写,初学者极有可能会写崩。这便是模拟题的一个特征:繁琐耗时。
首先针对识别数字写出如下函数:
bool recog(char ipt){
if('0'<=ipt && ipt<='9') return true;
if('a'<=ipt && ipt<='z') return false;
return false;
}
其实最后一行不必要(数据是可靠的),但是不同的编译器有的会警告,有的不给过编译(如牛客网的在线编译器),故需要加上。
接下来根据流程图针对每一个需要扩展的字符串子串作扩展,为了保留必要的信息,输入的字符串为“?-?”(?为合法字符)的形式:
const string s1 = "abcdefghijklmnopqrstuvwxyz";
const string s2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const string nums = "0123456789";
void myfill(string& ipt,int p1,int p2,int p3){
string ori = ipt;
string ret;
if(!(recog(ipt[0])^recog(ipt[2])) && ipt[0] < ipt[2]){
bool isNum = recog(ipt[0]);
ret.resize((ipt[2]-ipt[0]-1)*p2);
int start,end;
if(!isNum){
start = ipt[0]-'a'+1;
end = ipt[2]-'a'-1;
}
else{
start = ipt[0]-'0'+1;
end = ipt[2]-'0'-1;
}
for(int i=0;i<(ipt[2]-ipt[0]-1);i++){
for(int j=0;j<p2;j++){
if(p1 == 3){ret.push_back('*');}
else if(isNum == true){
if(p3 == 1){ret.push_back(nums[i+start]);}
else ret.push_back(nums[end-i]);
}
else if(isNum == false){
if(p3 == 1){
if(p1 == 1)ret.push_back(s1[i+start]);
if(p1 == 2)ret.push_back(s2[i+start]);
}
else if(p3 == 2){
if(p1 == 1)ret.push_back(s1[end-i]);
if(p1 == 2)ret.push_back(s2[end-i]);
}
}
}
}
}
else{ret = "-";}
ipt = ret;
}
选择结构流程图与前文略有区别:
我时常把字符串的函数和向量的函数搞混,但它们确有不少相似之处:
字符串常用函数:
find(str, pos)
:在字符串中查找子串str
,从位置pos
开始。replace(old_str, new_str)
:将字符串中的old_str
替换为new_str
。tolower()
:将字符串中的字母转换为小写。toupper()
:将字符串中的字母转换为大写。append(str)
:将字符串str
追加到当前字符串str0
末尾。(等价于str0+=str)substr(pos, len)
:从字符串中提取子串,从位置pos
开始,长度为len
。compare(str)
:比较字符串与str
的大小关系。sort()
:对字符串中的字符进行排序。length()
:获取字符串的长度。clear()
:清空字符串内容。向量常用函数:
at(index)
:访问指定位置的元素。push_back(val)
:在向量末尾添加元素val
。pop_back()
:删除向量末尾的元素。sort()
:对向量中的元素进行排序。reverse()
:颠倒向量中元素的顺序。size()
:获取向量中元素的个数。clear()
:清空向量中的所有元素。insert(pos, val)
:在指定位置插入元素val
。erase(pos)
:删除指定位置的元素。find(val)
:查找向量中是否存在元素val
。replace(old_val, new_val)
:将向量中的old_val
替换为new_val
。copy(vecit1,vecit2,cpyvecit)
:将向量vec
两个迭代器代表的位置和中间位置的元素复制到cpyvec
的迭代器代表的位置上。神奇的是,向量的一些函数也能用在字符串上,比如对于string str,我们可以用str.size()获取str的长度,这与str.length()等效。同时也可以用push_back往str里传回char类型的数据,这会被接在str的末尾。而且,如果要复制字符串子串,除了用substr(),我们可以用迭代器完成这种操作:
string cpystr = str.substr(0,2);
这等价于构造函数:
string cpystr = string(str.begin(),str.begin()+2);
只可惜复制子串,向量里的copy()函数并不能在字符串里发挥作用(我写这题时尝试过)。
接下来去尝试去构建结果字符串:以-作为插板,把原字符串拆分成几部分,并记录每次-出现时,其前后的字符,并将其拼接成myfill函数的输入。
void extension(string ipt,int p1,int p2,int p3){
vector<string> strs;
vector<string> extensions;
int begin = 0;
for(int i=1;i<ipt.length();i++){
if(ipt[i]=='-'){
strs.push_back(ipt.substr(begin,i-begin));
extensions.push_back(ipt.substr(i-1,3));
//push_back里面的写法可以改成string(ipt.begin()+begin,ipt.begin()+i-1)
begin = i+1;
}
}
strs.push_back(ipt.substr(begin,ipt.size()-begin));
for(int i=0;i<extensions.size();i++){
myfill(extensions[i],p1,p2,p3);
}
string ret;
ret = ret + strs[0];
for(int i=1;i<strs.size();i++){
ret = ret + extensions[i-1];
ret = ret + strs[i];
}
std::cout << ret;
}
最后写一下主函数:
int main(){
string ipt;
int p1,p2,p3;
cin >> p1 >> p2 >> p3 >> ipt;
extension(ipt,p1,p2,p3);
return 0;
}
把这些代码拼在一块就结束了?结果放到牛客上测试用例都没过。我不知道牛客的编译器是怎么想的,在myfill()函数中,利用resize()函数是为了预留空间,在本地这个代码运行没有问题,在牛客上后面的push_back()居然没有在预留的空间里置入元素而是又开了空间放入元素,这直接导致extensions里面的元素在牛客上无法正常显示,非常坑。
把myfill()这一点修复之后就又行了?结果只过了80%的测试用例。不过这至少说明我们代码的基本逻辑和语法是没问题的,只是没考虑特殊情况:我们没有考虑'-'连在一块和出现在开头末尾的情况,这些情况题目没有特地说明,但题目强调了需要扩展的情况,那么剩下的这些烂摊子就不要动,稍微修改extension函数,完整代码如下:
#include <bits/stdc++.h>
using namespace std;
const string s1 = "abcdefghijklmnopqrstuvwxyz";
const string s2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const string nums = "0123456789";
bool recog(char ipt){
if('0'<=ipt && ipt<='9') return true;
if('a'<=ipt && ipt<='z') return false;
return false;
}
void myfill(string& ipt,int p1,int p2,int p3){
string ori = ipt;
string ret;
if(!(recog(ipt[0])^recog(ipt[2])) && ipt[0] < ipt[2]){
bool isNum = recog(ipt[0]);
// ret.resize((ipt[2]-ipt[0]-1)*p2);
int start,end;
if(!isNum){
start = ipt[0]-'a'+1;
end = ipt[2]-'a'-1;
}
else{
start = ipt[0]-'0'+1;
end = ipt[2]-'0'-1;
}
for(int i=0;i<(ipt[2]-ipt[0]-1);i++){
for(int j=0;j<p2;j++){
if(p1 == 3){ret.push_back('*');}
else if(isNum == true){
if(p3 == 1){ret.push_back(nums[i+start]);}
else ret.push_back(nums[end-i]);
}
else if(isNum == false){
if(p3 == 1){
if(p1 == 1)ret.push_back(s1[i+start]);
if(p1 == 2)ret.push_back(s2[i+start]);
}
else if(p3 == 2){
if(p1 == 1)ret.push_back(s1[end-i]);
if(p1 == 2)ret.push_back(s2[end-i]);
}
}
}
}
}
else{ret = "-";}
ipt = ret;
}
void extension(string ipt,int p1,int p2,int p3){
vector<string> strs;
vector<string> extensions;
int begin = 0;
for(int i=1;i<ipt.length()-1;i++){
if(ipt[i]=='-' && ipt[i-1]!='-' && ipt[i+1]!='-'){
strs.push_back(ipt.substr(begin,i-begin));
extensions.push_back(ipt.substr(i-1,3));
//push_back里面的写法可以改成string(ipt.begin()+begin,ipt.begin()+i-1)
begin = i+1;
}
}
strs.push_back(ipt.substr(begin,ipt.size()-begin));
for(int i=0;i<extensions.size();i++){
myfill(extensions[i],p1,p2,p3);
}
string ret = "";
ret = ret + strs[0];
for(int i=1;i<strs.size();i++){
ret = ret + extensions[i-1];
ret = ret + strs[i];
}
printf("%s",ret.c_str());
}
int main(){
string ipt;
int p1,p2,p3;
cin >> p1 >> p2 >> p3 >> ipt;
extension(ipt,p1,p2,p3);
return 0;
}
这段代码放上去才能AC。
感谢你能看到这里。