记事本

注:本题有较多的部分分,请参看数据规模部分。

题目描述

记事本是 Windows 平台下一款经典的文本编辑器,其存储文件的扩展名为 .txt,文件属性没有任何格式标签或者风格,所以相当适合在 DOS 环境中编辑。

在本题中,可能会用到的按键如下图所示:

光标移动

光标表示当前要进行输入等操作的位置,在本题中,我们假设所有字符都是等宽的。

光标的位置可以用行列坐标来描述,光标所在的行和列均从1开始,例如:

以下操作可以进行光标的移动,使用 MOVE <comd> 输入相关的操作,其中 <comd> 表示指令,可以使用以下字符串代替:

  • Home:把光标移动到当前行的开头。
  • End:把光标移动到当前行的末尾。
  • Up:光标移动到上一行的相同列。
    • 若当前为第一行,则不进行任何操作。
    • 若上一行的列数小于当前光标的列数,则将光标移动到上一行的末尾。
  • Down:光标移动到下一行的相同列。
    • 若当前为最后一行,则不进行任何操作。
    • 若下一行的列数小于当前光标的列数,则将光标移动到下一行的末尾。
  • Left:光标左移一位。
    • 若当前光标位于记事本开始,则不进行任何操作。
    • 若当前光标处于某一行的开头,则将光标移动到上一行的末尾。
  • Right:光标右移一位。
    • 若当前光标位于记事本末尾,则不进行任何操作。
    • 若当前光标处于某一行的末尾,则将光标移动到下一行的开头。

输入

以下操作可以在光标后进行输入,使用 INSERT <comd> 输入相关的操作,其中 <comd> 表示指令,可以使用以下字符串代替:

  • Char <char>:输入一个字符,其中 <char> 是输入的字符。

    • <char> 可能是一下字符中的任意一个:

    注:下列字符中不包含空格与换行符

    `1234567890-=~!@#$%^&*()_+qwertyuiop[]\QWERTYUIOP{}|asdfghjkl;'ASDFGHJKL:"zxcvbnm,./ZXCVBNM<>?
    
    • 例如:INSERT Char a 表示在当前光标后插入 a 字符。
  • Enter:输入换行符,并进行换行。

  • Space:输入空格。

  • Paste:在当前光标后,插入粘贴板中的内容,若粘贴板中无内容,则忽略当前操作。

删除

以下操作可以删除记事本中的内容,使用 REMOVE <comd> 输入相关的操作,其中 <comd> 表示指令,可以使用以下字符串代替:

  • Del:删除当前光标位置之后的一个字符。
    • 若该字符为换行符,则当前行与下一行合并为一行。
    • 若当前光标在文件末尾,则忽略当前操作。
  • Backspace:删除当前光标位置之前的一个字符。
    • 若该字符为换行符,则当前行与上一行合并为一行。
    • 若当前光标在文件开头,则忽略当前操作。

粘滞功能(分数占比 24分)

输入SHIFT指令,可以启动或关闭粘滞功能。

  • 开始时粘滞功能默认为关闭状态,之后每次点击:

    • 若当前为启动状态,则关闭;
    • 若当前为关闭状态,则启动。
  • 粘滞功能启动时,记录当前的光标位置为 记录点

  • 粘滞功能关闭时,若此时的光标位置与 记录点 的位置不同,则进入选中状态

  • 粘滞功能启动后,直到功能关闭前,不会对记事本进行除光标移动外的任何操作

当进入选中状态后,通过记录点与当前光标,可以唯一的确定一段内容,现令记录点与光标之间的所有字符(包括换行符)为 选中字段

例如,记录点位于第1行第2列,光标位于第2行第4列时,选中字段如下图所示:

当前 处于选中状态 时,对于不同的情况,需要按照序号依次执行以下操作:

  • 若进行光标移动

    1. 退出选中状态
    2. 尝试进行光标的移动(无论光标最终是否移动,都会退出选中状态)。
  • 若进行输入

    1. 将选中内容替换为输入内容;
    2. 退出选中状态
  • 若进行删除

    1. 删除当前选中内容;
    2. 退出选中状态
  • 若再次启动粘滞功能退出选中状态,但保留上一次选中字段的 记录点 作为当前记录点

  • 若进行查找,字数统计,复制,打印操作,则在操作后仍然保持选中状态

查找

输入FIND <word> 指令,进行字符串查找,其中 <word> 为输入的要查找的字符串,该字符串中不包含空格与换行符。

执行该指令时,要根据当前是否处于选中状态做不同的处理:

  • 若当前处于选中状态:查找输入字符串在选中字段中的出现次数并输出。
  • 否则:查找输入字符串在当前记事本中的出现次数并输出。

例如:当前没有选中的内容,且记事本中的内容为 ababa,若执行 FIND aba,则应当输出2,分别在第1列与第3列出现过。

字数统计

输入COUNT 指令,进行字数统计。

执行该指令时,要根据当前是否处于选中状态做不同的处理:

  • 若当前处于选中状态:输出当前选中字段中的可见字符(不包括空格与换行符)的数量。
  • 否则:输出当前文档中可见字符(不包括空格与换行符)的数量。

复制

输入COPY指令,进行复制操作。

执行该指令时,要根据当前是否处于选中状态做不同的处理:

  • 若当前处于选中状态:复制选中字段到粘贴板;
  • 否则,
    • 若当前行不为空:复制当前行的内容(不包括换行符)到粘贴板;
    • 否则:忽略当前操作。

打印

输入PRINT 指令,输出当前的记事本中的全部内容,并在之后输出一个换行符。

输入格式

输入包含 n+1n+1 行。
第一行包含一个整数 nn,表示接下来指令的数量。
接下来 nn 行,每行一条指令,格式形如题目描述中的叙述。

输出格式

对于需要输出的指令,进行相应的输出。
若为 FINDCOUNT 操作,输出一行表示相应的数字。
若为 PRINT操作,则输出若干行,表示记事本的当前内容,并在之后输出一个换行。

请注意:所有的输出不要有多余的空格。

测试样例

样例输入

20
INSERT Char #
INSERT Enter
INSERT Char C
INSERT Enter
INSERT Space
INSERT Char _
INSERT Char _
PRINT
INSERT Char >
INSERT Enter
INSERT Char h
INSERT Char h
INSERT Char h
INSERT Enter
PRINT
COUNT
FIND __
REMOVE Del
REMOVE Backspace
PRINT

样例输出

#
C
 __
#
C
 __>
hhh

8
1
#
C
 __>
hhh

数据规模

对于 100%100\% 的测试数据,1n50001 \le n \le 5000

不同测试点所包含的功能不同,其具体情况如下表所示。

点击此处下载额外的测试用例

#include <bits/stdc++.h>
using namespace std;

enum comd {
    home,
    en,  // end
    le,  // left
    ri,  // right
    up,
    down,
    del,
    backspace
};

string txt, copyTxt;
int remember = -1, now = -1;  //选区,string的下标,当执行shift就更新remember=now
bool selected = false, shifted = false, haveCopy = false;

int move(comd com) {
    selected = false;
    if (com == le) {
        if (now == -1)  //如果光标在开头,则不能左移
            return 0;
        now--;
        return 1;
    } else if (com == ri) {
        if (now == txt.size() - 1)  //如果在文档末尾
            return 0;
        now++;
        return 1;
    } else if (com == home) {  //前往上一行换行符后
        int t = 0;
        while (now > -1 && txt[now] != '\n')
            now--,t++;
        return t;
    } else if (com == en) {  //前往本行换行符前
        int t = 0;
        while (now + 1 != txt.size() && txt[now + 1] != '\n')
            now++,t++;
        return t;
    } else if (com == up) {
        auto col = move(home);
        move(le);
        move(home);
        while (col > 0 && now + 1 != txt.size() && txt[now + 1] != '\n')
            move(ri),col--;
    } else if (com == down) {
        auto col = move(home);
        move(en);
        if (!move(ri))
            move(home);
        while (col > 0 && now + 1 != txt.size() && txt[now + 1] != '\n')
            move(ri),col--;
    }
    return 0;
}

//插入字符串,是否从剪贴板插入由主函数决定是否c=copyTxt
void insert(string c) {
    if (!selected) {
        txt.insert(now + 1, c);
        now += c.size();
    } else {                                                       //如果选中了
        int ks = min(now, remember) + 1, js = max(now, remember);  // ks是选区第一个字符的下标,js是选区最后一个字符的下标
        txt.replace(ks, js - ks + 1, c);                           //替换选区内容
        selected = false;
        now = ks + c.size() - 1;//更新光标为替换后的字符串的最后一个字符的下标
    }
}

void remove(comd com) {
    if (!selected) {
        if (com == del) {
            if (now + 1 != txt.size())
                txt.erase(now + 1, 1);
        } else if (com == backspace)
            if (now != -1)
                txt.erase(now--, 1);
    } else {
        int ks = min(now, remember) + 1, js = max(now, remember);  // ks是选区第一个字符的下标,js是选区最后一个字符的下标
        txt.erase(ks, js - ks + 1);
        selected = false;
        now = ks - 1;
    }
}

void find(string word) {
    int ks = 0, js = txt.size() - 1, cnt = 0, len = word.size();
    if (selected) {
        ks = min(now, remember) + 1;
        js = max(now, remember);
    }
    while (ks + len-1 <= js)//有可能选区只有1个字符,然后find也是1个字符
        if (txt.substr(ks++, len) == word)
            cnt++;
    printf("%d\n", cnt);
}

void count() {
    int ks = 0, js = txt.size() - 1, cnt = 0;
    if (selected) {
        ks = min(now, remember) + 1;
        js = max(now, remember);
    }
    while (ks <= js) {
        if (txt[ks] != '\n' && txt[ks] != ' ')
            cnt++;
        ks++;
    }
    printf("%d\n", cnt);
}

void copy() {
    if (selected) {
        copyTxt = txt.substr(min(now, remember) + 1, abs(now - remember));
        haveCopy = true;
    } else {
        int saveNow = now;
        int ks = move(home), num = move(en);
        now = saveNow;
        if (num == 0)
            return;
        copyTxt = txt.substr(now - ks + 1, num);
        haveCopy = true;
    }
}

int main() {
    int n;
    string op, wo, tmp;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        cin >> op;
        if (op == "MOVE") {
            cin >> wo;
            if (wo == "Home") {
                move(home);
            } else if (wo == "End") {
                move(en);
            } else if (wo == "Up") {
                move(up);
            } else if (wo == "Down") {
                move(down);
            } else if (wo == "Left") {
                move(le);
            } else if (wo == "Right") {
                move(ri);
            }
        } else if (op == "INSERT") {
            cin >> wo;
            if (wo == "Char") {
                cin >> tmp;
                insert(tmp);
            } else if (wo == "Enter") {
                insert("\n");
            } else if (wo == "Space") {
                insert(" ");
            } else if (wo == "Paste") {
                if (haveCopy)
                    insert(copyTxt);
            }
        } else if (op == "REMOVE") {
            cin >> wo;
            if (wo == "Del") {
                remove(del);
            } else
                remove(backspace);
        } else if (op == "SHIFT") {
            if (shifted) {
                shifted = false;
                if (remember != now) 
                    selected = true;
            } else {
                shifted = true;
                if (selected) {
                    selected = false;
                } else
                    remember = now;
            }
        } else if (op == "FIND") {
            cin >> wo;
            find(wo);
        } else if (op == "COUNT") {
            count();
        } else if (op == "COPY") {
            copy();
        } else if (op == "PRINT") {
            cout << txt << '\n';
        }
    }
    return 0;
}