算法竞赛入门经典(第二版)-刘汝佳-第六章 数据结构基础 习题(12/14)

说明

本文是我对第六章14道习题的练习总结,建议配合紫书——《算法竞赛入门经典(第2版)》阅读本文。
另外为了方便做题,我在VOJ上开了一个contest,欢迎一起在上面做:第六章习题contest
如果想直接看某道题,请点开目录后点开相应的题目!!!

习题

习6-1 UVA 673 平衡的括号

思路
这么简单的题目竟然错了3次,羞愧一下!注意各种细节的可能性,尤其是循环结束后stack应该为空!
给出一组测试数据,希望有帮助:
3
)
()
)(
代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stack>
using namespace std;

stack<char> st;

int main(void)
{
    int t;
    cin >> t;
    getchar();
    while (t --) {
        string s;
        getline(cin, s);
        while (st.size()) st.pop();
        bool res = true;
        for (int i = 0; i < s.size(); i ++) {
            char c = s[i];
            if (c == '(' || c == '[')
                st.push(c);
            else {
                if (st.empty() || c == ')' && st.top() != '('
                     || c == ']' && st.top() != '[') {
                    res = false;
                    break;
                } else
                    st.pop();
            }
        }
        if (res && st.empty()) puts("Yes");
        else puts("No");
    }

    return 0;
}

习6-2 UVA 712 S - 树

思路
由查询可直接计算出叶子位置,对应方法为查询的二进制数转换为10进制数,但需要注意这个题x1 x2 … xN的顺序会有所改变,还需要加一步映射。详见代码。
代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;

const int N = 7;

int n;
int order[N+1];
string leaf;

int main(void)
{
    int t = 0;
    while (cin >> n && n) {
        char s[N+1];
        for (int i = 0; i < n; i ++) {
            scanf("%s", s);
            order[i] = s[1]-'1';
        }
        cin >> leaf;

        string res;
        int k;
        cin >> k;
        while (k --) {
            scanf("%s", s);
            int m = 0;
            for (int i = 0; i < n; i ++)
                m = m*2 + s[order[i]]-'0';
            res += leaf[m];
        }
        printf("S-Tree #%d:\n", ++t);
        cout << res << endl;
        if (t) puts("");
    }

    return 0;
}

习6-3 UVA 536 二叉树重建

思路
首先可以将A-Z映射到0-25,这样数组链表就可以直接表示。
根据两个不同序遍历的重建是比较简单的,主要思想是先序和后序的根分别在最前面和最后面,然后找出根在另一个序遍历中的位置,从而知道左子树和右子树的大小,从而递归建树。
代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;

const int N = 26;
    
int lt[N+1], rt[N+1];
    
int c2d(char c)
{       
    return c - 'A' + 1;
}           
        
char d2c(int d)
{       
    return d - 1 + 'A';
}       
    
int rebuild(int n, int pre[], int in[])
{   
    if (n == 0) return 0;
    int root = pre[0];
    if (n == 1) {
        lt[root] = rt[root] = 0;
        return root;
    }
    int m;
    for (m = 0; m < n; m ++) {
        if (in[m] == root) break;
    }
    lt[root] = rebuild(m, pre+1, in);
    rt[root] = rebuild(n-m-1, pre+m+1, in+m+1);
    return root;
}

void post(int root)
{
    if (root == 0) return;
    post(lt[root]);
    post(rt[root]);
    printf("%c", d2c(root));
}

int main(void)
{
    int n;
    char s1[N+1], s2[N+1];
    int pre[N+1], in[N+1];
    while (scanf("%s%s", s1, s2) != EOF) {
        n = strlen(s1);
        for (int i = 0; i < n; i ++) {
            pre[i] = c2d(s1[i]);
            in[i] = c2d(s2[i]);
        }

        int root = rebuild(n, pre, in);
        post(root);
        printf("\n");
    }

    return 0;
}

习6-4 UVA 439 骑士的移动

思路
基本的BFS。需要注意两种特殊情况:
1、起点与终点重合的情况;
2、从起点无法到达终点的情况。
我写程序时经常容易漏掉对第一种情况的判断。
代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
using namespace std;

const int N = 8;
const int INF = 0x3f3f3f3f;

typedef pair<int, int> P;

P bg, ed;

bool legal(int x, int y)
{
    return x >= 0 && x < N && y >= 0 && y < N;
}

int BFS()
{
    int d[N][N];
    for (int i = 0; i < N; i ++)
        for (int j = 0; j < N; j ++)
            d[i][j] = INF;

    queue <P> q;
    q.push(bg);
    d[bg.first][bg.second] = 0;
    while( q.size() ) {
        P p = q.front();
        q.pop();
        int x = p.first;
        int y = p.second;

        if (p == ed)
            return d[x][y];

        int pos[8][2] = {{1, 2}, {-1, 2}, {2, 1}, {2, -1},
            {1, -2}, {-1, -2}, {-2, 1}, {-2, -1}};
        for (int i = 0; i < 8; i ++) {
            int nx = x + pos[i][0];
            int ny = y + pos[i][1];
            if (legal(nx, ny) && d[nx][ny] == INF) {
                q.push(P(nx, ny));
                d[nx][ny] = d[x][y]+1;
            }
        }
    }

    return INF;
}

int main(void)
{
    char s1[3], s2[3];
    while (~scanf("%s%s", s1, s2)) {
        bg = P(s1[0]-'a', s1[1]-'1');
        ed = P(s2[0]-'a', s2[1]-'1');

        printf("To get from %s to %s takes %d knight moves.\n", s1, s2, BFS());
    }

    return 0;
}

习6-5 UVA 1600 巡逻机器人

思路
机器人要从一个m*n(1≤m,n≤20)网格的左上角(1,1)走到右下角(m,n)。网格中的一些格子是空地(用0表示),其他格子是障碍(用1表示)。机器人每次可以往4个方向走一格,但最多连续地穿越k(0≤k≤20)个障碍,求最短路长度。起点和终点保证是空地。

此题与例6-14 UVA 816 Abbott 的复仇思路上是非常类似的,都是以三维向量标识状态。不过这个题在实现细节上要比例题要简单。
本题中(x, y, t)标识可能出现的某一状态,x表示横坐标,y表示纵坐标,t表示已经经过的障碍数,t不应当超过k。注意如果只用(x, y)来标识状态会出错。
代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;

const int N = 20;
const int INF = 0x3f3f3f3f;

int dir[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};

struct Node {
    int x, y, t;
    int d;
    Node(){}
    Node(int xx, int yy, int tt) : x(xx), y(yy), t(tt){}
};

int m, n, k;
Node mp[N+1][N+1][N+1];
int v[N+1][N+1];
Node *bg, *ed;

void init_mp()
{
    for (int x = 1; x <= m; x ++) {
        for (int y = 1; y <= n; y ++) {
            int vv = 0;
            scanf("%d", &vv);
            v[x][y] = vv;
            for (int t = 0; t <= k; t ++) {
                mp[x][y][t].x = x;
                mp[x][y][t].y = y;
                mp[x][y][t].t = t;
                mp[x][y][t].d = INF;
            }
        }
    }
    bg = &mp[1][1][0];
    ed = &mp[m][n][0];
}

bool legal(int x, int y)
{
    return x >= 1 && x <= m && y >= 1 && y <= n;
}

int BFS()
{
    queue<Node*> que;
    que.push(bg);
    bg->d = 0;

    while( que.size() ) {
        Node *p = que.front();
        que.pop();
        //printf("%d,%d,%d,%d\n", p->x, p->y, p->t, p->d);

        if (p->x == ed->x && p->y == ed->y)
            return p->d;

        for (int i = 0; i < 4; i ++) {
            int nx = p->x + dir[i][0];
            int ny = p->y + dir[i][1];
            if (legal(nx, ny)) {
                int nt = v[nx][ny] ? p->t+1 : 0;
                Node *np = &mp[nx][ny][nt];
                if (np->d == INF && np->t <= k) {
                    np->d = p->d + 1;
                    que.push(np);
                }
            }
        }
    }
    return -1;
}

int main(void)
{
    int kase;
    scanf("%d", &kase);
    while (kase--) {
        scanf("%d%d%d", &m, &n, &k);
        init_mp();

        printf("%d\n", BFS());
    }

    return 0;
}

习6-6 UVA 12166 修改天平(未尝试)

思路

代码



习6-7 UVA 804 Petri 网模拟

思路
题意:
你的任务是模拟Petri网的变迁。Petri网包含NP个库所(用P1,P2…表示)和NT个变迁(用T1,T2…表示)。0 < NP, NT<100。当每个变迁的每个输入库所都至少有一个token时,变迁是允许的。变迁发生的结果是每个输入库所减少一个token,每个输出库所增加一个token。变迁的发生是原子性的,即所有token的增加和减少应同时进行。注意,一个变迁可能有多个相同的输入或者输出。如果一个库所在变迁的输入库所列表中出现了两次,则token会减少两个。输出库所也是类似。如果有多个变迁是允许的,一次只能发生一个。
输入一个Petri网络。初始时每个库所都有一个token。每个变迁用一个整数序列表示,负数表示输入库所,正数表示输出库所。每个变迁至少包含一个输入和一个输出。最后输入一个整数NF,表示要发生NF次变迁(同时有多个变迁允许时可以任选一个发生,输入保证这个选择不会影响最终结果)。

解法:循环模拟,对当前库所状态搜索所有变迁,当找到符合条件的变迁时(使变迁后所有库所包含的token都不低于0),即发生变迁。输入输出细节见代码。
代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int NP = 100;
const int NT = 100;

struct Node {
    int k;
    struct Node *next;
};

int np, nt, nf;
int p[NP], p1[NP], t[NT][NP];

int main()
{
    int kase = 0;
    while (scanf("%d", &np) && np) {
        for (int i = 1; i <= np; i++)
          scanf("%d", &p[i]);
        scanf("%d", &nt);
        memset(t, 0, sizeof(t));
        for (int i = 1; i <= nt; i++) {
            int j;
            while (scanf("%d", &j) && j) {
                if (j > 0) t[i][j]++;
                else t[i][-j]--;
            }
        }
        scanf("%d", &nf);

        int f = 1;
        for (; f <= nf; f++) {
            int i;
            for (i = 1; i <= nt; i++) {
                int j;
                for (j = 1; j <= np; j++) {
                    p1[j] = p[j]+t[i][j];
                    if (p1[j] < 0) break;
                }
                if (j > np) {
                    memcpy(p, p1, sizeof(p1));
                    break;
                }
            }
            if (i > nt) break;
        }

        if (f > nf) printf("Case %d: still live ", ++kase);
        else printf("Case %d: dead ", ++kase);
        printf("after %d transitions\nPlaces with tokens:", f-1);
        for (int i = 1; i <= np; i++)
            if (p[i] > 0) printf(" %d (%d)", i, p[i]);
        printf("\n\n");
    }

    return 0;
}

习6-8 UVA 806 空间结构

思路
题意:
黑白图像有两种表示法:点阵表示和路径表示。路径表示法首先需要把图像转化为四分树,然后记录所有黑结点到根的路径。
NW、NE、SW、SE分别用1、2、3、4表示。最后把得到的数字串看成是五进制的,转化为十进制后排序。例如上面的树在转化、排序后的结果是:9 14 17 22 23 44 63 69 88 94 113。
你的任务是在这两种表示法之间进行转换。在点阵表示法中,1表示黑色,0表示白色。图像总是正方形的,且长度n为2的整数幂,并满足n≤64。输入输出细节请参见原题。

两种状态互转都需要用递归进行处理:

  1. 点阵转路径表示的情况。递归时判断当前区域包含的点是否全黑或全白,如果是则终止递归,并存储全黑叶节点对应的路径。其中递归时以字符串存储当前从根到该节点的路径。如果该区域包含的点有黑有白,则将当前区域分成4个递归处理,同时字符串加上当前路径。
  2. 路径转点阵表示的情况。递归时几乎是第一种情况的相反处理,但需要判断该节点是否是全黑叶节点,如果是则当前节点对应的区域全部置成’*'并终止递归。

另外本题需要注意一些输入输出的细节:

  1. 输出十进制时每个数字以空格分隔,但还要每12个数换行输出。
  2. 最后一个case后无空行。
    代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <string>
#include <set>
using namespace std;

const int N = 64+1;

int n;
char area[N][N];
vector<int> nums;
set<string> strs;

bool same_color(int x0, int y0, int x1, int y1)
{
    char color0 = area[x0][y0];
    for (int i = x0; i < x1; i ++)
        for (int j = y0; j < y1; j ++)
            if (area[i][j] != color0) return false;
    return true;
}

int str2num(string s)
{
    int res = 0;
    for (int i = 0; i < s.size(); i++)
        res = res*5 + s[i]-'0';
    return res;
}

void get_nums(int x0, int y0, int x1, int y1, string s)
{
    if (same_color(x0, y0, x1, y1)) {
        if (area[x0][y0] == '1') nums.push_back(str2num(s));
        return;
    }
    int mx = (x0+x1) / 2;
    int my = (y0+y1) / 2;
    get_nums(x0, y0, mx, my, '1'+s);
    get_nums(x0, my, mx, y1, '2'+s);
    get_nums(mx, y0, x1, my, '3'+s);
    get_nums(mx, my, x1, y1, '4'+s);
}

void set_black(int x0, int y0, int x1, int y1)
{
    for (int i = x0; i < x1; i ++)
        for (int j = y0; j < y1; j ++)
            area[i][j] = '*';
}

string num2str(int num)
{
    string s;
    while (num) {
        char tmp = num%5 + '0';
        s = tmp + s;
        num /= 5;
    }
    return s;
}

void set_area(int x0, int y0, int x1, int y1, string s)
{
    if (strs.count(s)) {
        set_black(x0, y0, x1, y1);
        return;
    }
    if (x0 + 1 == x1 && y0 + 1 == y1) return;
    int mx = (x0+x1) / 2;
    int my = (y0+y1) / 2;
    set_area(x0, y0, mx, my, '1'+s);
    set_area(x0, my, mx, y1, '2'+s);
    set_area(mx, y0, x1, my, '3'+s);
    set_area(mx, my, x1, y1, '4'+s);
}

int main(void)
{
    //freopen("input", "r", stdin);
    int kase = 0;
    while (scanf("%d", &n) && n) {
        if (kase) printf("\n");
        printf("Image %d\n", ++kase);
        if (n > 0) {
            for (int i = 0; i < n; i++)
                scanf("%s", area[i]);
            nums.clear();
            get_nums(0, 0, n, n, "");
            sort(nums.begin(), nums.end());
            for (int i = 0; i < nums.size(); i++)
                printf("%d%c", nums[i], (i == nums.size()-1 || i % 12 == 11) ? '\n' : ' ');
            printf("Total number of black nodes = %d\n", nums.size());
        } else {
            n = -n;
            strs.clear();
            int x; 
            while (scanf("%d", &x) && x != -1)
                strs.insert(num2str(x));
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    area[i][j] = '.';
                }
                area[i][n] = '\0';
            }
            set_area(0, 0, n, n, "");
            for (int i = 0; i < n; i++)
                printf("%s\n", area[i]);
        }
    }
    
    return 0;
}

习6-9 UVA 127 纸牌游戏

思路
题意
把52张牌从左到右排好,每张牌自成一个牌堆(pile)。当某张牌与它左边那张牌或者左边第3张牌“match”(花色suit或者点数rank相同)时,就把这张牌移到那张牌上面。移动之后还要查看是否可以进行其他移动。只有位于牌堆顶部的牌才能移动或者参与match。当牌堆之间出现空隙时要立刻把右边的所有牌堆左移一格来填补空隙。如果有多张牌可以移动,先移动最左边的那张牌;如果既可以移一格也可以移3格时,移3格。按顺序输入52张牌,输出最后的牌堆数以及各牌堆的牌数。

数据结构:用双向链表+vector数组。用链表的原因是需要删除一部分节点(当然这个题目只有52个节点,直接用顺序结构估计也可以通过)。双向链表的删除操作更容易实现,而且这个题目同时需要向前和向后访问。vector数组用来存储每一堆纸牌。

代码架构:每次从头对链表中节点进行顺序搜索,如果其左数第三个节点与其花色或点数相同,则发生纸牌移动,并重新循环;如果左数第一个相同,则发生移动并重新循环;否则继续搜索。如果搜索到链表结尾仍然没有找到符合移动条件的,说明纸牌已经无法移动。
但我的思路存在可优化的地方(只是已经AC了就懒得重新写):发生纸牌移动后不需要从头再开始找,而是直接关注移动后的纸牌能否再次往前移动即可。
代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

const int N = 52;

typedef pair<char, char> P;

vector<P> pile[N+1];
int pre[N+1], next[N+1];

void init()
{
    for (int i = 1; i <= N; i++) {
        pre[i] = i-1;
        next[i-1] = i;
        pile[i].clear();
    }
    pre[0] = N;
    next[N] = 0;
}

int pre3(int u)
{
    int cnt = 3;
    while (u && cnt) {
        u = pre[u];
        cnt--;
    }
    return u;
}

bool same_top(int u, int v)
{
    P a = pile[u][pile[u].size()-1];
    P b = pile[v][pile[v].size()-1];
    return a.first == b.first || a.second == b.second;
}

void del(int u)
{
    next[pre[u]] = next[u];
    pre[next[u]] = pre[u];
}

void move_top(int u, int v)
{
    int lenu = pile[u].size();
    P a = pile[u][lenu-1];
    pile[v].push_back(a);
    pile[u].resize(lenu-1);
    if (lenu-1 == 0) del(u);
}

int main(void)
{
    char s[3];
    while (scanf("%s", s) && strcmp(s, "#")) {
        init();
        for (int i = 1; i <= N; i++) {
            if (i > 1) scanf("%s", s);
            pile[i].push_back(P(s[0], s[1]));
        }

        while (true) {
            int u = next[0];
            while (u) {
                bool flag = true;
                int v = pre3(u);
                if (v && same_top(u, v)) move_top(u, v);
                else if ((v = pre[u]) && same_top(u, v)) move_top(u, v);
                else flag = false;
                if (flag) break;
                u = next[u];
            }
            if (!u) break;
        }

        vector<int> res;
        int u = next[0];
        while (u) {
            res.push_back(pile[u].size());
            u = next[u];
        }
        if (res.size() == 1) printf("1 pile remaining:");
        else printf("%d piles remaining:", res.size());
        for (int i = 0; i < res.size(); i++)
            printf(" %d", res[i]);
        printf("\n");
    } 
    
    return 0;
}   

习6-10 UVA 246 10-20-30

思路
题意
给52张的扑克堆,先从左往右发7张牌,之后连续不断从左往右发7张牌,如果有牌堆形成了以下3种情况(按顺序判断):

  1. 头两张+尾一张和为10或20或30
  2. 头一张+尾两张和为10或20或30
  3. 尾三张和为10或20或30

就把这三张牌拿走,放到总牌堆底(这步要不断执行直到不再满足条件或牌堆没了)。如果有一个牌堆因为这个操作被取完了,那么以后将不在这个位置发牌。如果最后7个牌堆都可以消掉,那么赢,总牌堆用完,那么输,否则平(即不断循环)。问最后的输赢平,并输出步数。

解法
模拟,用一个vector记录下7个牌堆和总牌堆,这样就可以用set去记录状态了,然后每个牌堆用一个双端队列deque表示,这样满足可以从头也可以从尾巴取,不断模拟即可。

另外这个题我在做的过程中犯了两个比较大的错误:

  1. 在判断和为10或20或30时应当是循环判断,而不是只判断一次
  2. 保存当前状态时应当同时包括手中的牌和牌堆,我开始只保存了牌堆状态,结果出现了很隐蔽的错误(示例数据都能过,但是WA)

同时提供一组测试数据供参考:
INPUT

2 6 5 10 10 4 10 10 10 4 5 10 4 5 10 9 7 6 1 7 6 9 5 3 10 10 4 10 9 2 1
10 1 10 10 10 3 10 9 8 10 8 7 1 2 8 6 1 2 3 5 7
4 3 2 10 8 10 6 8 9 5 8 10 5 3 5 4 6 9 9 1 7 6 3 5 10 10 8 10 9 10 10 7 10 1 10 10 10 3 10 9 8 10 8 7 1 2 8 6 7 3 3 8
10 5 4 3 5 7 10 8 2 3 9 10 8 4 5 1 7 6 7 2 6 9 10 2 3 10 3 4 4 9 10 1 1
10 1 10 10 10 3 10 9 8 10 8 7 1 2 8 6 7 3 3 8
4 3 2 10 8 10 6 8 9 5 8 10 5 3 5 4 6 9 9 1 7 6 3 5 10 10 8 10 9 10 10 7
10 1 10 10 10 3 10 9 8 10 8 7 1 2 8 6 1 2 3
0

OUTPUT

Win : 180
Loss: 112
Loss: 118
Loss: 232

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<deque>
#include<set>
#include<vector>
using namespace std;

typedef deque<int> PILE;
typedef vector<PILE> PILES;
PILES piles;

void init()
{
    piles.clear();
    for (int i = 0; i < 8; i++) {
        PILE p;
        piles.push_back(p);
    }
}

void solve()
{
    set<PILES> state;
    PILE& hand = piles[7];

    int cur = 0;
    while (hand.size()) {
        PILE& p = piles[cur];
        p.push_back(hand.front()); hand.pop_front();

        while (p.size() >= 3) {
            int n = p.size();
            if ((p[0]+p[1]+p[n-1]) % 10 == 0) {
                hand.push_back(p[0]);
                hand.push_back(p[1]);
                hand.push_back(p[n-1]);
                p.pop_front();
                p.pop_front();
                p.pop_back();
            } else if ((p[0]+p[n-2]+p[n-1]) % 10 == 0) {
                hand.push_back(p[0]);
                hand.push_back(p[n-2]);
                hand.push_back(p[n-1]);
                p.pop_front();
                p.pop_back();
                p.pop_back();
            } else if ((p[n-3]+p[n-2]+p[n-1]) % 10 == 0) {
                hand.push_back(p[n-3]);
                hand.push_back(p[n-2]);
                hand.push_back(p[n-1]);
                p.pop_back();
                p.pop_back();
                p.pop_back();
            } else break;
        }

        if (hand.size() == 52) {
            printf("Win : %d\n", state.size()+8);
            return;
        }

        if (hand.empty()) {
            printf("Loss: %d\n", state.size()+8);
            return;
        }

        if (state.count(piles)) {
            printf("Draw: %d\n", state.size()+8);
            return;
        }

        state.insert(piles);
        do {
            cur = (cur+1)%7;
        } while (piles[cur].empty());
    }
}

int main()
{
    int num;
    while (scanf("%d", &num) && num) {
        init(); 
        PILE& hand = piles[7];
        hand.push_back(num);
        for (int i = 0; i < 51; i++) {
            scanf("%d", &num);
            hand.push_back(num);
        }       
        for (int i = 0; i < 7; i++) {
            piles[i].push_back(hand.front()); hand.pop_front();
        }       
                
        solve();
    }       
        
    return 0;
}

习6-11 UVA 10410 树重建

思路
题意
输入一个n(n≤1000)结点树的BFS序列和DFS序列,你的任务是输出每个结点的子结点列表。输入序列(不管是BFS还是DFS)是这样生成的:当一个结点被扩展时,其所有子结点应该按照编号从小到大的顺序访问。

本以为与二叉树根据两种遍历重建树的过程完全类似,试了一下才发现大相径庭。
在反复推演后,形成了本题的思路:
基于BFS序列按层遍历,对于该层的节点在进行顺序访问时,应当在DFS序列中顺序相同(但可能中间隔着其它节点),一旦BFS中的节点在DFS中找不到对应的,说明已经到达树的下一层。
按层遍历BFS序列时,其上层节点序列已经存储到set < int > upper中,访问该层的节点时,寻找其在DFS序列中向前找最近的出现在upper中的节点,这就是其父节点,将该节点归入其父节点的孩子集合中。
最后对每个节点输出其孩子集合即可。

此题网上还有很多其他解法,有兴趣的读者请参考其他博客。
代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
using namespace std;

const int N = 1001;

int n;
int bfs[N], dfs[N];
vector<int> children[N];

void get_tree()
{
    set<int> upper;
    upper.insert(bfs[1]);
    int begin = 2, i, j;
    while (begin <= n) {
        i = begin;
        j = 2;
        for (; i <= n; i++) {
            for (; j <= n; j++)
                if (dfs[j] == bfs[i]) break;
            if (j > n) break;
            for (int k = j-1; k >= 1; k--) {
                if (upper.count(dfs[k])) {
                    children[dfs[k]].push_back(bfs[i]);
                    break;
                }
            }
        }
        upper.clear();
        for (int k = begin; k < i; k++)
            upper.insert(bfs[k]);
        begin = i;
    }
}

int main(void)
{
    while (scanf("%d", &n) != EOF) {
        for (int i = 1; i <= n; i++)
            scanf("%d", &bfs[i]);
        for (int i = 1; i <= n; i++)
            scanf("%d", &dfs[i]);

        for (int i = 1; i <= n; i++)
            children[i].clear();
        get_tree();
        for (int i = 1; i <= n; i++) {
            printf("%d:", i);
            for (int j = 0; j < children[i].size(); j++)
                printf(" %d", children[i][j]);
            printf("\n");
        }
    }

    return 0;
}

习6-12 UVA 810 筛子难题

思路
题意是给出一个图,每个格子上都有一个数。然后给出一个起始位置,往这个位置上放一个骰子。然后给出这个骰子的初始状态,骰子的状态由两个数字表示,分别代表骰子顶面和正前面(从二维地图从下向上看为正前)的点数。如果骰子所在格子的相邻格子的数字等于当前骰子顶面的点数相同或者-1,那么骰子就可以滚动到这个格子上,如果格子的数字为0则表示该格子无法到达。要求求出一条路,使得骰子从起点出发能再走回起点,如果不存在,输出“No Solution Possible”。

其实就是个四维状态BFS,每个状态(x, y, t, f)表示(横坐标, 纵坐标, 顶面数字, 正前面数字)。实际山给定顶面数字和正前面数字就唯一的确定了一个筛子的状态。四个方向(上下左右)的翻滚中,左翻和右翻需要用打表法确定下一个状态。
另外本题的输出格式略显复杂,详见代码。
代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;

const int N = 10;
const int INF = 0x3f3f3f3f;

int dir[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; //下右上左
int r[7][7];

struct Node {
    int x, y, t, f;
    int d;
    Node *pre;
};

int m, n;
Node mp[N+1][N+1][6+1][6+1];
int v[N+1][N+1];
Node *bg;

void init_right()
{
    memset(r, 0, sizeof(r));
    r[6][2] = r[2][1] = r[1][5] = r[5][6] = 4;
    r[6][5] = r[5][1] = r[1][2] = r[2][6] = 3;
    r[6][3] = r[3][1] = r[1][4] = r[4][6] = 2;
    r[6][4] = r[4][1] = r[1][3] = r[3][6] = 5;
    r[3][2] = r[2][4] = r[4][5] = r[5][3] = 6;
    r[3][5] = r[5][4] = r[4][2] = r[2][3] = 1;
}

void init_mp()
{
    for (int x = 1; x <= m; x ++) {
        for (int y = 1; y <= n; y ++) {
            scanf("%d", &v[x][y]);
            for (int t = 1; t <= 6; t ++) {
                for (int f = 1; f <= 6; f ++) {
                    mp[x][y][t][f].x = x;
                    mp[x][y][t][f].y = y;
                    mp[x][y][t][f].t = t;
                    mp[x][y][t][f].f = f;
                    mp[x][y][t][f].d = INF;
                    mp[x][y][t][f].pre = NULL;
                }
            }
        }
    }
}

bool legal(int x, int y)
{
    return x >= 1 && x <= m && y >= 1 && y <= n;
}

Node *BFS()
{
    queue<Node*> que;
    que.push(bg);
    bg->d = 0;

    bool first = true;
    while( que.size() ) {
        Node *p = que.front();
        que.pop();
        //printf("%d,%d,%d,%d\n", p->x, p->y, p->t, p->d);

        if (!first) {
            if (p->x == bg->x && p->y == bg->y)
                return p;
        } else
            first = false;

        for (int i = 0; i < 4; i ++) {
            int nx = p->x + dir[i][0];
            int ny = p->y + dir[i][1];
            if (legal(nx, ny) && (v[nx][ny] == -1 || v[nx][ny] == p->t)) {
                int nt, nf, t = p->t, f = p->f;
                if (i == 0) { //down
                    nt = 7-f; nf = t;
                } else if (i == 1) { //right
                    nt = 7-r[t][f]; nf = f;
                } else if (i == 2) { //up
                    nt = f; nf = 7-t;
                } else if (i == 3) { //left
                    nt = r[t][f]; nf = f;
                }
                Node *np = &mp[nx][ny][nt][nf];
                if (np->d == INF || np == bg) {
                    np->d = p->d + 1;
                    np->pre = p;
                    que.push(np);
                }
            }
        }
    }
    return NULL;
}
    
int main(void)
{   
    init_right();
    
    char name[21];
    while (scanf("%s", name) && strcmp(name, "END")) {
        int bx, by, bt, bf;
        scanf("%d%d%d%d%d%d", &m, &n, &bx, &by, &bt, &bf);
        init_mp();
        bg = &mp[bx][by][bt][bf];
            
        printf("%s\n", name);
        Node *p = BFS();
        if (p == NULL) printf("  No Solution Possible\n");
        else {
            typedef pair<int, int> P;
            vector<P> path; 
            while (p->pre != bg) {
                path.push_back(P(p->x, p->y)); 
                p = p->pre; 
            }   
            path.push_back(P(p->x, p->y));
            path.push_back(P(bg->x, bg->y));
            for (int i = path.size()-1; i >= 0; i--) {
                if ((path.size()-i)%9 == 1) printf("  ");
                printf("(%d,%d)", path[i].first, path[i].second);
                if (i != 0) printf(",");
                if ((path.size()-i)%9 == 0 || i == 0) printf("\n");
            }
        }
    }

    return 0;
}

习6-13 UVA 215 电子表格计算器

思路
题意
在一个R行C列(R≤20,C≤10)的电子表格中,行编号为A~T,列编号为0~9。按照行优先顺序输入电子表格的各个单元格。每个单元格可能是整数(可能是负数)或者引用了其他单元格的表达式(只包含非负整数、单元格名称和加减号,没有括号)。表达式保证以单元格名称开头,内部不含空白字符,且最多包含75个字符。
尽量计算出所有表达式的值,然后输出各个单元格的值(计算结果保证为绝对值不超过10000的整数)。如果某些单元格循环引用,在表格之后输出(仍按行优先顺序)。

表格的引用类似于拓扑排序过程,如果出现循环引用则说明拓扑上存在圈。本来直接用书中例题的拓扑排序能解,但本题还要求输出所有无法计算的单元格,这个就需要对书中的排序算法做一点扩展,详见code。

这个题上折腾了挺长时间,为了帮助大家调试,特提供一组测试数据:
INPUT

1 1
A0-A0
4 4
A1-A2+A3
A2
A3
B1+6
A3
4
A3-A2+6
B2+7-15+B1
1
2
3
4
C1-C3-C3-C3-15-C3
8
5
6
3 3
A1
A2
B0
B1
B2
C0
C1
C2
A0
3 3
A1
A2
B0
B1
B2
6
A1
C2
A1
6 6
1
A2
A0
C2
C4+C5+C2
B4-B3
2
A1
A1
A1
B0
B1
3
D5-C0+4
A2+B6-C0+E5+5
4
5
6
4
1
5
10
15
20
5
A2
A2
A2
A2
A3
6
B4
4
5
6
7
6 6
1
A2
A0
C2
C4+C5+C2
B4-B3
2
A1
A1
A1
B0
B1
3
D5-C0+4
A2+B5-C0+E5+5
4
5
6
4
1
5
10
15
20
5
A2
A2
A2
A2
A0
6
B4
4
5
6
7
0 0

OUTPUT

A0: A0-A0

      0     1     2     3
A    10    10    10    10
B    10     4     6     2
C     1     2     3     4
D   -29     8     5     6

A0: A1
A1: A2
A2: B0
B0: B1
B1: B2
B2: C0
C0: C1
C1: C2
C2: A0

      0     1     2
A     6     6     6
B     6     6     6
C     6     6     6

A3: C2
A4: C4+C5+C2
C2: A2+B6-C0+E5+5
E5: A3

      0     1     2     3     4     5
A     1     1     1     5    16     1
B     2     1     1     1     2     1
C     3    21     5     4     5     6
D     4     1     5    10    15    20
E     5     1     1     1     1     1
F     6     2     4     5     6     7


代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

const int ROW = 20;
const int COL = 10;
const int N = ROW*COL;
const int MAX = 10000;

int row, col;
string tab[ROW][COL];

int n;
int c[N];
int val[N];
typedef pair<int, int> P;
vector<P> dep[N];

void prase_str(int i, int j)
{
    int k = i*col+j;
    string& s = tab[i][j];
    val[k] = 0;
    dep[k].clear();
    if (!isalpha(s[0])) {
        val[k] = atoi(s.c_str());
    } else {
        for (int h = 0; h < s.size(); h++) {
            if (isalpha(s[h]) || s[h] == '-' || s[h] == '+') {
                int sign = 1;
                if (s[h] == '-' || s[h] == '+') {
                    if (s[h] == '-') sign = -1;
                    h++;
                }
                if (isalpha(s[h]))
                  dep[k].push_back(P(sign, (s[h]-'A')*col + s[h+1]-'0'));
                else
                  val[k] += sign * atoi(s.substr(h).c_str());
            }
        }
    }
}

int dfs(int k)
{
    c[k] = -1;
    if (val[k] >= MAX) return MAX;
    for (int i = 0; i < dep[k].size(); i++) {
        int sign = dep[k][i].first;
        int u = dep[k][i].second;
        int v = 0;
        if (c[u] < 0 || (v = dfs(u)) >= MAX)
            return val[k] = MAX;
        val[k] += sign*v;
    }
    c[k] = 1;
    dep[k].clear();
    return val[k];
}

void toposort()
{
    memset(c, 0, sizeof(c));
    for (int i = 0; i < n; i++)
        dfs(i);
}

bool check_val()
{
    for (int i = 0; i < n; i++)
        if (val[i] >= MAX) return false;
    return true;
}

int main(void)
{
    //freopen("input", "r", stdin);
    while (scanf("%d%d", &row, &col), row || col) {
        n = row*col;
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                cin >> tab[i][j];
                prase_str(i, j);
            }
        }

        toposort();
        if (check_val()) {
            printf(" ");
            for (int j = 0; j < col; j++)
              printf("%6d", j);
            printf("\n");
            for (int i = 0; i < row; i++) {
                printf("%c", i+'A');
                for (int j = 0; j < col; j++) {
                    printf("%6d", val[i*col+j]);
                }
                printf("\n");
            }
        } else {
            for (int i = 0; i < row; i++) {
                for (int j = 0; j < col; j++) {
                    if (val[i*col+j] >= MAX) {
                        printf("%c%c: ", i+'A', j+'0');
                        cout << tab[i][j] << endl;
                    }
                }
            }
        }
        printf("\n");
    }

    return 0;
}

习6-14 UVA 12118 检察员的难题(未尝试)

思路

代码



  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大学生参加学科竞赛有着诸多好处,不仅有助于个人综合素质的提升,还能为未来职业发展奠定良好基础。以下是一些分析: 首先,学科竞赛是提高专业知识和技能水平的有效途径。通过参与竞赛,学生不仅能够深入学习相关专业知识,还能够接触到最新的科研成果和技术发展趋势。这有助于拓展学生的学科视野,使其对专业领域有更深刻的理解。在竞赛过程中,学生通常需要解决实际问题,这锻炼了他们独立思考和解决问题的能力。 其次,学科竞赛培养了学生的团队合作精神。许多竞赛项目需要团队协作来完成,这促使学生学会有效地与他人合作、协调分工。在团队合作中,学生们能够学到如何有效沟通、共同制定目标和分工合作,这对于日后进入职场具有重要意义。 此外,学科竞赛是提高学生综合能力的一种途径。竞赛项目通常会涉及到理论知识、实际操作和创新思维等多个方面,要求参赛者具备全面的素质。在竞赛过程中,学生不仅需要展现自己的专业知识,还需要具备创新意识和解决问题的能力。这种全面的综合能力培养对于未来从事各类职业都具有积极作用。 此外,学科竞赛可以为学生提供展示自我、树立信心的机会。通过比赛的舞台,学生有机会展现自己在专业领域的优势,得到他人的认可和赞誉。这对于培养学生的自信心和自我价值感非常重要,有助于他们更加积极主动地投入学习和未来的职业生涯。 最后,学科竞赛对于个人职业发展具有积极的助推作用。在竞赛中脱颖而出的学生通常能够引起企业、研究机构等用人单位的关注。获得竞赛奖项不仅可以作为个人履历的亮点,还可以为进入理想的工作岗位提供有力的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值