悲剧文本——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; // 移动光标
}