习题与总结

天平 (UVa 839 Not so Mobile)

  • 递归向下:遇到重量为 0 就深入子树

  • 回溯向上:子树返回自己的总重量和平衡状态

  • 自底向上计算:叶子节点先计算,父节点依赖子节点的结果

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

// 递归函数:返回该天平是否平衡,并通过引用返回总重量
bool solve(int& totalWeight) {
    int wl, dl, wr, dr;
    cin >> wl >> dl >> wr >> dr;
    
    bool leftBalanced = true;
    bool rightBalanced = true;
    int leftWeight = wl;
    int rightWeight = wr;
    
    // 如果左边是子天平,递归处理
    if (leftWeight == 0) {
        leftBalanced = solve(leftWeight);
    }
    
    // 如果右边是子天平,递归处理
    if (rightWeight == 0) {
        rightBalanced = solve(rightWeight);
    }
    
    // 计算当前天平的总重量
    totalWeight = leftWeight + rightWeight;
    
    // 检查是否平衡:力矩相等且左右子树都平衡
    return (leftWeight * dl == rightWeight * dr) && leftBalanced && rightBalanced;
}

int main() {
    int t;
    cin >> t;
    
    for (int i = 0; i < t; i++) {
        int totalWeight; // 存储总重量,但这里主要用不到
        if (solve(totalWeight)) {
            cout << "YES" << endl;
        } else {
            cout << "NO" << endl;
        }
        
        // 测试用例之间用空行分隔,这里只是输出时处理
        if (i < t - 1) {
            cout << endl; // 题目要求输出用例间空行
        }
    }
    
    return 0;
}

Abbott的复仇 (UVA816 Abbott's Revenge)

详解

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

struct Node {
    int r, c, dir;  // 行,列,方向
    Node(int r=0, int c=0, int dir=0): r(r), c(c), dir(dir) {}
};

// 方向:N,E,S,W
const int dr[] = {-1, 0, 1, 0};
const int dc[] = {0, 1, 0, -1};
const char* dir_name = "NESW";
const char* turn_name = "FLR";

int dir_id(char c) {
    for (int i = 0; i < 4; i++)
        if (dir_name[i] == c) return i;
    return 0;
}

int turn_id(char c) {
    for (int i = 0; i < 3; i++)
        if (turn_name[i] == c) return i;
    return 0;
}

// 主要函数
void solve() {
    string name;
    int r0, c0, r2, c2;
    char dir0;
    
    cin >> name;
    cin >> r0 >> c0 >> dir0 >> r2 >> c2;
    
    // 实际起点是进入的第一个位置
    int dir = dir_id(dir0);
    int r1 = r0 + dr[dir];
    int c1 = c0 + dc[dir];
    
    // 存储规则:has_rule[r][c][dir][turn]
    bool rule[10][10][4][3] = {false};
    
    // 读入规则
    int r, c;
    while (cin >> r && r) {
        cin >> c;
        string s;
        while (cin >> s && s != "*") {
            int dir = dir_id(s[0]);
            for (int i = 1; i < s.length(); i++) {
                int turn = turn_id(s[i]);
                rule[r][c][dir][turn] = true;
            }
        }
    }
    
    // BFS
    int dist[10][10][4];
    Node parent[10][10][4];
    memset(dist, -1, sizeof(dist));
    
    queue<Node> q;
    Node start(r1, c1, dir);
    dist[r1][c1][dir] = 0;
    q.push(start);
    
    Node end_node(-1, -1, -1);  // 记录终点
    
    while (!q.empty()) {
        Node u = q.front(); q.pop();
        
        // 到达终点
        if (u.r == r2 && u.c == c2) {
            end_node = u;
            break;
        }
        
        // 尝试三种转向
        for (int turn = 0; turn < 3; turn++) {
            if (rule[u.r][u.c][u.dir][turn]) {
                int new_dir = u.dir;
                if (turn == 0);       // 直行,方向不变
                else if (turn == 1)   // 左转
                    new_dir = (u.dir + 3) % 4;
                else if (turn == 2)   // 右转
                    new_dir = (u.dir + 1) % 4;
                
                int nr = u.r + dr[new_dir];
                int nc = u.c + dc[new_dir];
                
                // 检查是否在迷宫内且未访问
                if (nr >= 1 && nr <= 9 && nc >= 1 && nc <= 9) {
                    if (dist[nr][nc][new_dir] < 0) {
                        dist[nr][nc][new_dir] = dist[u.r][u.c][u.dir] + 1;
                        parent[nr][nc][new_dir] = u;
                        q.push(Node(nr, nc, new_dir));
                    }
                }
            }
        }
    }
    
    // 输出结果
    cout << name << endl;
    
    if (end_node.r == -1) {
        cout << "  No Solution Possible" << endl;
    } else {
        // 重建路径
        vector<Node> path;
        Node cur = end_node;
        
        // 回溯到起点
        while (true) {
            path.push_back(cur);
            if (cur.r == r1 && cur.c == c1 && cur.dir == dir) break;
            cur = parent[cur.r][cur.c][cur.dir];
        }
        
        // 添加起点
        path.push_back(Node(r0, c0));
        
        // 反向输出
        for (int i = path.size()-1, cnt = 0; i >= 0; i--) {
            if (cnt % 10 == 0) cout << " ";
            cout << " (" << path[i].r << "," << path[i].c << ")";
            if (++cnt % 10 == 0) cout << endl;
        }
        if (path.size() % 10 != 0) cout << endl;
    }
}

int main() {
    while (true) {
        solve();
        string end_check;
        if (!(cin >> end_check) || end_check == "END") break;
    }
    return 0;
}

Petri网模拟 (UVA804 Petri Net Simulation)

任务是模拟Petri网的运转。在这个网中会有N个P(Place),即"仓库"。还会有N个T(Transitions),即"中转站"。仓库中会有一些token(图中的小黑球),他们需要通过管道(例图中的箭头)在Petri网中移动。

运转规则是: 对于一个中转站,它的运转要求他的输入端(指向他的箭头)至少满足每个输入端有一个token。例如例图中的T2需要P2中至少有两个token才能发生运转。 运转发生后,中转站的每个输入端减少一个token,它的每个输出端增加一个token。

例如例图中,当P2有两个token,T2发生运转,则P2清空,P3增加一个token。注意!当有同时多个中转站满足运转要求,则可以任选一个运转,输入会保证结果唯一.

请问输入的Petri网是否能运转到指定次数?

理解题面后,解题十分容易,直接按照题意处理即可。循环遍历每个中转站,如果可以运转,则运转,即检查一个Trans的输入端的Place中的token数量是否满足要求,运转后并将运转次数++,然后把一个Trans输出端的Place相应增加一些token。如果所有中转站都无法运转,则运转结束。

需要注意一下,每个Transition的输入当中可能有多个不一样序号的Place,同一个Place也可能会有多次输入同一个Transition中。输出也是类似的。所以我的解题代码当中,一个Trans的结构用map存放了序号对应的个数,并用vector记录了有哪些Place。

其次注意一下,每组数据的输出需要和下组输出之间多空一行。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

struct Transition {
    vector<pair<int, int>> input;   // (place, weight)
    vector<pair<int, int>> output;  // (place, weight)
};

int main() {
    int NP, NT, NF;
    int caseNum = 1;
    
    while (cin >> NP && NP) {
        // 读取初始令牌
        vector<int> tokens(NP + 1);
        for (int i = 1; i <= NP; i++) {
            cin >> tokens[i];
        }
        
        // 读取变迁
        cin >> NT;
        vector<Transition> trans(NT + 1);
        
        for (int i = 1; i <= NT; i++) {
            int k, p, w;
            
            // 输入弧
            cin >> k;
            for (int j = 0; j < k; j++) {
                cin >> p >> w;
                trans[i].input.push_back({p, w});
            }
            
            // 输出弧
            cin >> k;
            for (int j = 0; j < k; j++) {
                cin >> p >> w;
                trans[i].output.push_back({p, w});
            }
        }
        
        // 模拟
        cin >> NF;
        int step;
        bool dead = false;
        
        for (step = 1; step <= NF; step++) {
            // 寻找第一个使能的变迁
            int fire_trans = 0;
            for (int t = 1; t <= NT; t++) {
                bool can_fire = true;
                
                // 检查所有输入弧
                for (auto &in : trans[t].input) {
                    if (tokens[in.first] < in.second) {
                        can_fire = false;
                        break;
                    }
                }
                
                if (can_fire) {
                    fire_trans = t;
                    break;
                }
            }
            
            // 如果没有使能的变迁
            if (fire_trans == 0) {
                dead = true;
                break;
            }
            
            // 执行变迁
            for (auto &in : trans[fire_trans].input) {
                tokens[in.first] -= in.second;
            }
            for (auto &out : trans[fire_trans].output) {
                tokens[out.first] += out.second;
            }
        }
        
        // 输出结果
        cout << "Case " << caseNum++ << ": ";
        if (dead) {
            cout << "dead after " << step - 1 << " transitions" << endl;
        } else {
            cout << "still live after " << NF << " transitions" << endl;
        }
        
        cout << "Places with tokens:";
        for (int i = 1; i <= NP; i++) {
            if (tokens[i] > 0) {
                cout << " " << i << " (" << tokens[i] << ")";
            }
        }
        cout << endl << endl;
    }
    
    return 0;
}

总结

主要学习目标

STL容器:vector、set和map这三个容器;string 和 stringstream;排序和检索相关函数。

栈、队列和优先队列的概念,并能用STL实现他们。

完全二叉树的数组实现,完全二叉树的链式表示法和数组表示法。

了解动态内存分配和释放方法及其注意事项。

掌握二叉树的先、中、后序遍历和层次遍历。

掌握图的DFS及连通块计数 和 BFS及最短路的输出

掌握拓扑排序算法与欧拉回路算法

相关推荐
2501_901147832 小时前
题解:有效的正方形
算法·面试·职场和发展·求职招聘
亲爱的非洲野猪2 小时前
动态规划进阶:状态机DP深度解析
算法·动态规划
dragoooon342 小时前
[hot100 NO.91~95]
算法
windows_62 小时前
【无标题】
算法
踢足球09293 小时前
寒假打卡:2026-01-24
数据结构·算法
亲爱的非洲野猪3 小时前
动态规划进阶:多维DP深度解析
算法·动态规划
AlenTech3 小时前
197. 上升的温度 - 力扣(LeetCode)
算法·leetcode·职场和发展
橘颂TA4 小时前
【Linux 网络】TCP 拥塞控制与异常处理:从原理到实践的深度剖析
linux·运维·网络·tcp/ip·算法·职场和发展·结构与算法
tobias.b4 小时前
408真题解析-2010-9-数据结构-折半查找的比较次数
java·数据结构·算法·计算机考研·408真题解析