再谈栈和队列
铁轨(Rails, ACM/ICPC CERC 1997, UVa 514)
题目描述
某城市有一个火车站。有n节车厢从A 方向驶入车站,按进站顺序编号为1~n。你的任务是判断是否能让它们按照某种特定的顺序进入B方向的铁轨并驶出车站。例如,出栈顺序(5,4,3,2,1)是可能的,但(5,4,1,2,3)是不可能的。
#include <cstdio>
#include <stack>
using namespace std;
const int MAXN = 1000 + 10;
int n, target[MAXN];
int main() {
while (scanf("%d", &n) == 1) {
stack<int> s;
int A = 1, B = 1;
for (int i = 1; i <= n; i++) {
scanf("%d", &target[i]);
}
int ok = 1;
while (B <= n) {
if (A == target[B]) {
A++;
B++;
} else if (!s.empty() && s.top() == target[B]) {
B++;
s.pop();
} else if (A <= n) {
s.push(A);
A++;
} else {
ok = 0;
break;
}
}
printf("%s\n", ok ? "Yes" : "No");
}
return 0;
}
矩阵链乘(Matrix Chain Multiplication, UVa 442)
题目描述
输入n个矩阵的维度和一些矩阵链乘表达式,输出乘法的次数。如果乘法无法进行,输出error。假定A是m*n矩阵,B是n*p矩阵,那么AB是m*p矩阵,乘法次数为m*n*p。如果A的列数不等于B的行数,则乘法无法进行。
例如,A是50*10的,B是10*20的,C是20*5的,则(A(BC))
的乘法次数为10*20*5(BC
的乘法次数)+50*10*5((A(BC))
的乘法次数)=3500。
分析
本题的关键是解析表达式,本题的表达式比较简单,可以用一个栈来完成:遇到子母时入栈,遇到右括号时出栈并计算,然后结果入栈。因输入保证合法,括号无需入栈。
#include <cstdio>
#include <stack>
#include <iostream>
#include <string>
using namespace std;
struct Matrix {
int a, b;
Matrix(int a = 0, int b = 0):a(a), b(b) {}
} m[26];
stack<Matrix> s;
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
string name;
cin >> name;
int k = name[0] - 'A';
cin >> m[k].a >> m[k].b;
}
string expr;
while (cin >> expr) {
int len = expr.length();
bool error = false;
int ans = 0;
for (int i = 0; i < len; i++) {
if (isalpha(expr[i]))
s.push(m[expr[i]-'A']);
else if (expr[i] == ')') {
Matrix m2 = s.top();
s.pop();
Matrix m1 = s.top();
s.pop();
if (m1.b != m2.a) {
error = true;
break;
}
ans += m1.a * m1.b * m2.b;
s.push(Matrix(m1.a, m2.b));
}
}
if (error)
printf("error\n");
else
printf("%d\n", ans);
}
return 0;
}
链表
破损的键盘(又名:悲剧文本)(Broken Keyboard, UVa 11988)
题目描述
输入包含多组数据。每组数据占一行,包含不超过100000个字母、下划线。字符“[”或者“]”。其中字符“[”表示Home键,“]”表示End键。输入结束标志为文件结束符(EOF)对于每组数据,输出一行,即屏幕上的悲剧文本。
样例输入:
This_is_a_[Beiju]_text
[[]][][]Happy_Birthday_to_Tsinghua_University
样例输出:
BeijuThis_is_a__text
Happy_Birthday_to_Tsinghua_University
分析
采用链表,没输入一个字符就把它保存起来,设输入的字符串是s[1~n],则可以用next[i]
表示在当前显示屏中s[i]右边的字符编号(即在s中的下标)。
为了方便起见,假设字符串s的最前面还有一个虚拟的s[0]
,则next[0]
就可以表示显示屏中最左边的字符。再用一个变量cur
表示光标的位置:即当前光标位于s[cur]
的右边。cur=0
说明光标位于“虚拟字符”s[0]
的右边,即显示屏的最左边。
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 100000 + 5;
int last, cur, next[maxn]; // 光标位于cur号字符的后面
char s[maxn];
int main() {
while (scanf("%s", s+1) == 1) {
int n = strlen(s+1); // 输入保存在s[1],s[2],s[3]…中
last = cur = 0;
next[0] = 0;
for (int i = 1; i <= n; i++) {
if (s[i] == '[') {
cur = 0;
} else if (s[i] ==']') {
cur = last;
} else {
next[i] = next[cur];
next[cur] = i;
if (cur == last) {
last = i; // 更新最后一个字符的编号
}
cur = i; // 移动光标
}
}
for (int i = next[0]; i != 0; i = next[i]) {
printf("%c",s[i]);
}
printf("\n");
}
return 0;
}
移动盒子(Boxes in a Line, UVa 12657)
题目描述
你有一行盒子,从左到右依次编号为1,2,3,…,n。可以执行以下四种指令:
- 1XY表示把盒子X移动到盒子Y左边(如果X已经在Y左边则忽略此指令)
- 2XY表示把盒子X移动到盒子Y右边(如果X已经在Y右边则忽略此指令)
- 3XY表示交换盒子X和盒子Y的位置
- 4表示反转整条链
指令保证合法,即X不等于Y。例如,当n=6时在初始状态下执行114后,盒子序列为2 3 1 4 5 6。接下来执行235,盒子序列变成2 1 4 5 3 6。再执行316,得到2 6 4 5 3 1。最终执行4,得到1 3 5 4 6 2。
输入包含不超过10组数据,每组数据第一行为盒子个数n和指令条数m(1<=n, m<=100000),以下m行每行包含一条指令。每组数据输出一行,即所有奇数位置的盒子编号之和。位置从左到右编号为1~n。
样例输入:
6 4
1 1 4
2 3 5
3 1 6
4
6 3
1 1 4
2 3 5
3 1 6
100000 1
4
样例输出:
Case 1:12
Case 2:9
Case 3:2500050000
分析
采用双向链表,用left[i]
和right[i]
分别表示编号为i的盒子左边和右边的盒子编号(如果是0,表示不存在),则下面的过程可以让两个结点相互连接:
void Link(int L, int R) {
right[L] = R;
left[R] = L;
}
有了这个代码,可以先记录好操作之前X和Y两边的结点,然后用Link函数按照某种顺序把它们连接起来。操作4比较特殊,为了避免一次修改所有元素的指针,此处增加一个标记inv
,表示有没有执行过操作4(如果inv=1
时再执行一次操作4,则inv变成0)。这样,当op为1和2且inv=1时,只需把op变成3-op
(注意操作3不受inv影响)即可。最终输出时要根据inv的值进行不同处理。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 100000 + 50;
int n;
int right[MAXN], left[MAXN];
void Link(int L, int R) {
right[L] = R;
left[R] = L;
}
int main() {
int m, kase = 0;
while (scanf("%d%d", &n, &m) == 2) {
for (int i = 1; i <= n; i++) {
left[i] = i-1;
right[i] = (i+1) % (n+1);
}
right[0] = 1;
left[0] = n;
int op, X, Y, inv = 0;
while (m--) {
scanf("%d", &op);
if (op == 4)
inv = !inv;
else {
scanf("%d%d", &X, &Y);
if (op == 3 && right[Y] == X)
swap(X, Y); // 交换位置时,使X在Y左边
//操作3不受翻转影响,先换位后翻转和先翻转后换位结果相同
if (op != 3 && inv)
op = 3 - op;
if (op == 1 && X == left[Y])
continue;
if (op == 2 && X == right[Y])
continue;
int LX = left[X], RX = right[X], LY = left[Y], RY = right[Y];
if (op == 1) {
Link(LX, RX);
Link(LY, X);
Link(X, Y);
}
else if (op == 2) {
Link(LX, RX);
Link(Y, X);
Link(X, RY);
}
else if (op == 3) {
if (right[X] == Y) {
Link(LX, Y);
Link(Y, X);
Link(X, RY);
}else {
Link(LX, Y);
Link(Y, RX);
Link(LY, X);
Link(X, RY);
}
}
}
}
int b = 0;
long long ans = 0;
for (int i = 1; i <= n; i++) {
b = right[b];
if (i%2)
ans += b;
}
if (inv && n%2 == 0)
ans = (long long)n*(n+1) / 2 - ans;
printf("Case %d: %lld\n", ++kase, ans);
}
return 0;
}