《算法竞赛入门经典》第6章数据结构基础书上例题(一)

44 篇文章 1 订阅

再谈栈和队列

铁轨(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;
} 
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值