「2023安徽小学组」文本编辑器 题解

 题目描述

李明正在学习使用文本编辑器软件 Vim。与 WordVSCode 等常用的键鼠配合使用的文本编辑器不同,Vim 采用了键盘操控+模式切换的设计。初始时用户处于普通模式,此时用户可以通过键盘输入命令执行光标移动等操作,或者输入特定命令进入到编辑模式;而在编辑模式中,用户可以从当前光标位置开始,向文本输入内容,然后按 ESC 键切换回普通模式

李明学习到了普通模式的如下指令。其中 (r,c) 为当前光标的位置,即第 r 行第 c 列,Lr 表示 r 行当前的长度,R 为当前文本总行数,数学符号 min\{x,y\} 表示 x 与 y 的最小值,例如 min\{2,4\}=2

从编辑模式按 ESC 键切换回普通模式时,光标位置位于编辑模式插入的最后一个字符处(假定每次进入编辑模式后至少向文本中插入一个字符)。初始时,Vim 处于普通模式,光标位置为 (1,1)

假设当前文本如下,光标位置用粗斜体+下划线+红色标注。李明的键盘输入序列 jlliHello^kIWorld^,其中 ^ 用于表示 ESC 键:

对于李明的键盘操作,jll 表示下移一格再右移两格,光标按 A->H->I->I 的顺序移动(其中第二次右移因为触及本行末尾无事发生)。此时光标位置为 (2,2),即第 2 行第 2 列。Vim 编辑器界面如下:

之后 iHello^ 表示从当前光标位置进入编辑模式,将插入的内容 Hello 置于此时光标位置 I 的左侧,然后按 ESC 键返回普通模式。此时光标位置为 (2,6),指向此次插入内容 Hello 的最后一个字符 oVim 编辑器界面如下。另外需注意,指令按键(例如本段示例中的小写字母 l)仅在普通模式下才表示编辑器控制指令,在编辑模式中则作为文本内容输入:

然后按 k 键将光标移至上一行。由于上一行的长度小于当前光标所在的列数,因此光标被移到了上一行的末尾。此时光标位置为 (1,3)

最后一段 IWorld^ 表示,按 I 键将光标移至本行最左侧的字母 A 处,进入编辑模式并将文本 World 插入到字母 A 的左端,然后按 ESC 退出编辑模式,光标停在插入的最后一个字母 d 处,坐标为 (1,5)

已知文本编辑器的初始内容,以及李明的键盘操作序列,你需要输出操作结束后,Vim 编辑器中的文本内容,以及最终光标所在的位置。


 输入格式

输入文件的第一行是一个正整数 N,表示初始时编辑器中文本的行数;之后 N 行,每行一个仅包含大小写字母的字符串,表示该行初始时的内容;之后一行为一个仅包含大小写字母和 ^ 的字符串,表示李明的键盘操作序列。输入保证操作序列结束后 Vim 处于普通模式,每次进入编辑模式时至少插入一个字符。


 输出格式

输出文件共 N+1 行。前 N 行依次为操作结束后文本编辑器中每一行的内容,最后一行为两个正整数 (r,c),表示操作结束后光标的位置。


 样例


样例输入

3
ABC
HI
OPQRS
jlliHello^kAWorld^jAXyz^jhhaUvw^

样例输出

WorldABC
HHelloIXyz
OPQUvwRS
3 6

 数据范围与提示

初始时第 i 行的长度为 L_i,操作序列的总长度为 LC。所有数据均满足:1 \le N,1 \le L_i \le 100,1 \le LC \le 10^4 测试数据的分布如下:


 提示与说明

注意区分数字 1、小写字母 l 与大写字母 I。本题中光标右移指令为小写字母 l,行首插入的指令为大写字母 I。 本题目的细节(例如光标的移动策略)与现实中的 Vim 编辑器存在差异,作答时以本题的描述为准。

 思路与部分实现

        首先注意到题目经常要求我们插入字符串,那么对于这种用心险恶的出题人,我们可以用 string 这神奇的数据类型帅他一脸(此题用 char 类型储存代码量要大很多 )

        其次,题目要求输出光标最后所在位置的坐标,这里考虑用 x,y 两个变量分别储存横纵坐标。确定了基本思路,接下来就是对题目要求的各种操作一一实现:

        如果当前字符 str[i] 是 '\text{h}',直接判断 y 是否大于 0:如果是,将横坐标 y 减一。

if(str[i]=='h')
    if(y>0) y--;


        如果当前字符 str[i] 是 '\text{j}',直接判断 x 是否小于 n-1:如果是,将纵坐标 x 加一,但由于 x 行的长度与 x+1 行的长度不一定相等,y 可能指向无,所以在 y 与 x+1 行的长度之间取较小值

if(str[i]=='j')
    if(x<n-1){
        int len=ans[++x].size()-1;
        //读入时下标从0开始,所以这里要减一
        y=min(y,len);
    }

         如果当前字符 str[i] 是 '\text{k}',处理步骤与上文基本一致,这里不做过多叙述。

if(str[i]=='k')
	if(x>0){
		int len=ans[--x].size()-1;
		y=min(y,len);
	}

         如果当前字符 str[i] 是 '\text{l}',直接判断 y 是否处于这一行的末尾:如果不是,将 y 加一。

if(str[i]=='l')
	if(y<ans[x].size()-1) y++;

         如果当前字符 str[i] 是 'a' 或 '\text{i}',就迎来了本题最大的难点,这里提供一个较为巧妙的思路:我们可以定义一个 string 类型的字符串 kk 以储存光标当前位置右侧的所有字符,以及 k 储存将要插入的字符,然后用 C++ 库中自带的 erase() 函数删除光标当前位置右侧的所有字符,接着更新横坐标于插入单词的末尾(即 k 的长度加此时 x 行的长度),最后在 x 行的末尾加入 kk 以及 kkk。至于 '\text{i}' 的情况怎么处理,只需在储存 kk 时将当前光标指向的字符也加入进去,并在之后一同删除,这就是处理 'a' 和 '\text{i}' 时的两个不同点。下面举个例子方便理解:

EHIAT

if(str[i]=='a')
    假设光标此时指向I且要插入的字符串为"XyZ",那么kk="AT",这行字符串删除光标右侧所有字符后为"EHI",
接着更新横坐标y指向5的位置(下标从0开始计算),最后在末尾加入kk和要插入的字符串,即:

    EHI + XyZ + AT = EHIXyZAT
EHIAT

if(str[i]=='i')
    假设光标此时指向I且要插入的字符串为"XyZ",那么kk="IAT",这行字符串删除光标指向的字符及光标右侧所有字符后为"EH",
接着更新横坐标y指向4的位置(下标从0开始计算),最后在末尾加入kk和要插入的字符串,即:

    EH + XyZ + IAT = EHXyZIAT

        最后提醒一点:千万注意操作的先后顺序! 

if(str[i]=='a'||str[i]=='i'){
	string k,kk;
    int L=str[i]=='a'?y+1:y;
	while(str[++i]!='^') kk+=str[i];
	for(int l=L;l<ans[x].size();l++) k+=ans[x][l];
	ans[x].erase(L);
    y=ans[x].size()+kk.size()-1;
    ans[x]+=kk+k;
    //一定要注意操作的先后顺序!
}

        如果当前字符 str[i] 是 '\text{A}', 直接在改行末尾加入 k(要插入的字符串),然后更新横坐标为当前行的末尾。

if(str[i]=='A'){
    string k;
    while(str[++i]!='^') k+=str[i];
	ans[x]+=k;
    y=ans[x].size()-1;
}

         如果当前字符 str[i] 是 '\text{I}',先更新横坐标为 k 的末尾,然后 ans[x]=k+ans[x]

if(str[i]=='I'){
	string k;
    while(str[++i]!='^') k+=str[i];
	y=k.size()-1;
    ans[x]=k+ans[x];
}

        到这里代码的主体部分就结束了,接下来请欣赏我那码风新奇的代码。

  完整代码

#include<iostream>
using namespace std;
string ans[105],str;
int main()
{
	int n,x=0,y=0;scanf("%d",&n);
	for(int i=0;i<n;i++) cin>>ans[i];
	cin>>str;
	for(int i=0;i<str.size();i++)
		if(str[i]=='h')
			if(y>0) y--;
		else if(str[i]=='j')
			if(x<n-1){
				int len=ans[++x].size()-1;
				y=min(y,len);
			}
		else if(str[i]=='k')
			if(x>0){
				int len=ans[--x].size()-1;
				y=min(y,len);
			}
		else if(str[i]=='l')
			if(y<ans[x].size()-1) y++;
		else if(str[i]=='a'||str[i]=='i'){
			string k,kk;int L=str[i]=='a'?y+1:y;
			while(str[++i]!='^') kk+=str[i];
			for(int l=L;l<ans[x].size();l++) k+=ans[x][l];
			ans[x].erase(L);y=ans[x].size()+kk.size()-1;ans[x]+=kk+k;
		}else if(str[i]=='A'){
			string k;while(str[++i]!='^') k+=str[i];
			ans[x]+=k;y=ans[x].size()-1;
		}else if(str[i]=='I'){
			string k;while(str[++i]!='^') k+=str[i];
			y=k.size()-1;ans[x]=k+ans[x];
		}
	for(int i=0;i<n;i++)
		cout<<ans[i]<<endl; 
	printf("%d %d",x+1,y+1);
	return 0;
} 

 总结

        题目看起来虽然复杂,实际上就是道简单的模拟,多注意细节即可。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值