Loading

悲剧文本——UVA 11988

问题描述 :

你有一个破损的键盘。键盘上的所有键都可以正常工作,但有时Home键或者End键会自动按下。你并不知道键盘存在这一问题,而是专心地输入英文单词,甚至连显示器都没瞧一眼。当你看显示器时,展现在你面前的是一段悲剧的文本。你的任务是计算这段文本有几个单词。

输入包含多组数据。每组数据占一行,包含不超过100000个英文字母、空格、字符“[”或者“]”(这多达20000个字符的数据会显示在一行,不用担心会换行)。其中字符“[”表示Home键(将光标定位到一行的开头),“]”表示End键(将光标定位到一行的结尾)。输入文件不超过5MB,对于每组数据,输出一行,即屏幕上的悲剧文本。

样例输入

This_is_a_[Beiju]_text
[[]][][]Happy_Birthday_to_Tsinghua_University
abc[123[456[ef

样例输出

BeijuThis_is_a__text
Happy_Birthday_to_Tsinghua_University
ef456123abc

使用STL

此题如果使用STL将会很简单,使用一个双端队列和栈即可。或者直接用双向链表。

但是STL会使程序变慢,如果有严格的时间限制,用STL可能得到TLE。

使用数组模拟

我们可以使用数组模拟一个链表。

具体的思路就是使用一个char str[]存储字符串数据,再用int next[]数组存储每个位置的后继节点在原数组中的下标,假设最后一个元素为l,next[l]=0。可能有些抽象,下面我们用几个示例说明。

为了简化代码,我们把str的第0个元素空出来,并让next的第0个元素等于链表的头节点在str中的下标。

char str[MAX_LEN + 1] = {0,'a','b','c'};
int next[MAX_LEN + 1] = {1,3,0,2};

确保理解了这个结构,来做几个问题。

1. 把上面用数组表示的链表转换成链式形式

2. 如何头插一个新节点,新节点在原数组中的位置是i(新节点为`str[i])?

3. 如何在第i个位置前插入一个新节点,新节点在原数组中的位置是j

答案

1. 'a'->'c'->'b'
    next[0] = 1 , str[1] = 'a'
    next[1] = 3 , str[3] = 'c'
    next[3] = 2 , str[2] = 'b'
    next[2] = 0 , 循环结束
    'a'->'c'->'b'
2. next[i] = next[0]
   next[0] = i

3. next[j] = next[i]
   next[i] = j

好,这时你大概已经理解如何用数组表示链表了,接下来就可以安排代码了。

对于此题,我们要关心的是两个状态,一是出现[时,这时就要转换到从链u顺序插入若干个字符,而当出现]时(或者根本未出现任何[时),又需要在链表末尾插入。这时候我们就需要用到两个变量,表示当前插入位置和链表尾的位置。

下面看看代码

int main() {
    int i,len,cur,last;
    char str[MAX_N];
    while (true) {
        while (scanf("%s", str+1) != EOF) {
            len = strlen(str);
			int next[MAX_N+1];
            cur = last = 0;
            next[0] = 0;
            for (i = 1; i < len; i++) {
                if (str[i] == '[') {
                    cur = 0;
                }
                else if (str[i] == ']') {
                    cur = last;
                }
                else {
                    next[i] = next[cur];
                    next[cur] = i;
                    if (cur == last) last = i;
                    cur = i;
                }
            }
            // 输出
            i = next[0];
            while (i!=0) {
				cout << str[i];
                i = next[i];
            }
            cout << endl;
		}
    }
    return 0;
}

这是网上广为流传的代码版本,我想大多数人疑惑的地方就是最后一个else里面的后继关系。一会我们用一个简短的数据推理一下。

这里先解释下代码,cur就是当前输入的位置,它是会变的,其实就相当于我们记录了我们上次操作的链表节点。last记录的是当前链表的尾巴的位置,方便]的时候直接找回去。

第一个if,我们需要到最前面插入,所以我们把cur设为0,相当于设置成链表的头节点(不是第一个节点,是用于寻找第一个节点的节点,这种实现方式数据结构中应该都学过)。

第二个if,我们回到最后,那就把cur设为last

else中的代码,我们看完示例大概就懂了。看这个示例的同时要看代码

In: "B[A]C"

开始循环 # _代表未赋值
cur=0,last=0,i=1
str ={ B[A]C}
next={0_____}

cur=1,last=1,i=2
str ={ B[A]C}
next={10____}

cur=0,last=1,i=3 #遇到了[
str ={ B[A]C}
next={10____}

cur=1,last=1,i=4
str ={ B[A]C}
next={30_1__}

cur=1,last=1,i=5 #遇到了]
str ={ B[A]C}
next={30_1__}

str ={ B[A]C}
next={35_10_}

end loop

按照这个顺序再输出,就是

ABC

同时我们会发现,next中的[]的位置都是未赋值的,也没人指向它们。

现在你大概已经理解了else中的意思,现在我写出来

else {
    next[i] = next[cur]; // 确保Home操作插入第一个后最开始的头不会丢失,和终止条件0的向后传递
    next[cur] = i; // 向cur右侧插入一个字符
    if (cur == last) last = i; // 更新最后一个字符编号
    cur = i; // 移动光标
}
posted @ 2020-11-03 21:36  yudoge  阅读(123)  评论(0编辑  收藏  举报