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

说明

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

例题

例6-1 UVA 210 并行程序模拟 (p139, 双端队列)

思路
这个题做了得有五六个小时吧,RE了3次,WA了一次。这种模拟题总感觉在理解上有歧义,也许有人一上来就理解对了,但我上来就没理解对(也可能因为原题描述不清楚),于是就一直没有正确的思路。

书中给出的题意是这样:

你的任务是模拟n个程序(按输入顺序编号为1~n)的并行执行。每个程序包含不超过25条语句,格式一共有5种:var = constant(赋值);print var(打印);lock;unlock;end。
变量用单个小写字母表示,初始为0,为所有程序公有(因此在一个程序里对某个变量赋值可能会影响另一个程序)。常数是小于100的非负整数。
每个时刻只能有一个程序处于运行态,其他程序均处于等待态。上述5种语句分别需要t1、t2、t3、t4、t5单位时间。运行态的程序每次最多运行Q个单位时间(称为配额)。当一个程序的配额用完之后,把当前语句(如果存在)执行完之后该程序会被插入一个等待队列中,然后处理器从队首取出一个程序继续执行。初始等待队列包含按输入顺序排列的各个程序,但由于lock/unlock语句的出现,这个顺序可能会改变。
lock的作用是申请对所有变量的独占访问。lock和unlock总是成对出现,并且不会嵌套。lock总是在unlock的前面。当一个程序成功执行完lock指令之后,其他程序一旦试图执行lock指令,就会马上被放到一个所谓的阻止队列的尾部(没有用完的配额就浪费了)。当unlock执行完毕后,阻止队列的第一个程序进入等待队列的首部。
输入n, t1, t2, t3, t4, t5, Q以及n个程序,按照时间顺序输出所有print语句的程序编号和结果。

我对题意补充一些说明:

1、变量始终为所有程序公有,根本没有某个程序对于变量的私有化处理,也就是说所谓的独占访问跟变量公有不矛盾。
2、unlock会使整个任务状态解除锁定。
3、执行完lock指令之后其他程序试图执行lock指令时,这个lock指令此时不会被执行。(我这个地方理解错了)

关于我编程中所犯错误的小结:

1、lock指令的处理不当,参考前面说明。
2、unlock时,阻止队列中并不一定有程序,这时应该进行判断(RE的原因)。

另外补充几个问题:

1、题目示例INPUT漏掉了测试数据组数。
2、最后一组数据后不应该输出空行。

最后,给出一组测试数据,供查错参考:
INPUT:

1

3 1 1 1 1 1 3
a = 5
print a
end
b = 7
lock
print a
print b
unlock
c = 10
print a
end
print a
print b
lock
print c
unlock
end

OUTPUT:

1: 5
2: 5
3: 5
3: 7
2: 7
3: 10
2: 5

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<sstream>
#include<algorithm>
#include<queue>
#include<deque>
#include<map>
using namespace std;

const int N = 1001;

int n, qt, t[5];
queue<string> prog[N];
deque<int> dq;
queue<int> que;
map<string, int> mp;

int cur, passed, locked;

void init_data()
{
    for (int i = 0; i < n; i++)
        while (prog[i].size()) prog[i].pop();
    while (dq.size()) dq.pop_front();
    while (que.size()) que.pop();
    mp.clear();
}

void run_prog()
{
    cur = dq.front();
    dq.pop_front();
    passed = 0;

    while (passed < qt && prog[cur].size()) {
        string s = prog[cur].front();
        //cout << cur+1 << "----------" << s << endl;
        string name, tmp;
        int val;
        if (s.find('=') != string::npos) {
            stringstream ss(s);
            ss >> name >> tmp >> val;
            mp[name] = val;
            passed += t[0];
        } else if (s.find("print") != string::npos) {
            stringstream ss(s);
            ss >> tmp >> name;
            printf("%d: %d\n", cur+1, mp[name]);
            passed += t[1];
        } else if (s == "lock") {
            if (locked) {
                que.push(cur);
                return;
            }
            locked = 1;
            passed += t[2];
        } else if (s == "unlock") {
            locked = 0;
            if (que.size()) {
                dq.push_front(que.front());
                que.pop();
            }
            passed += t[3];
        } else if (s == "end") {
            return;
        }
        prog[cur].pop();
    }
    dq.push_back(cur);
}

int main()
{
    int kase;
    scanf("%d", &kase);
    while (kase --) {
        scanf("%d", &n);
        for (int i = 0; i < 5; i++)
            scanf("%d", &t[i]);
        scanf("%d", &qt);
        getchar();
        init_data();
        string s;
        for (int i = 0; i < n; i++) {
            do {
                getline(cin, s);
                prog[i].push(s);
            } while (s != "end");
            dq.push_back(i);
        }   
            
        locked = 0;
        while (dq.size()) {
            run_prog(); 
            //printf("passed = %d, dq.size = %d\n", passed, dq.size());
        }       
            
        if (kase) printf("\n");
    }       
        
    return 0;
}

例6-2 UVA 514 铁轨 (p140, 栈)

思路
先进后出为栈,详细分析见书中例题部分。
代码

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

const int N = 1000;

int n, a[N+1];
stack<int> s;

int main(void)
{
    while (cin >> n && n) {
        while (cin >> a[1] && a[1]) {
            for (int i = 2; i <= n; i ++)
                scanf("%d", &a[i]);
            int i = 1, j = 1;
            while (i <= n) {
                while (!s.empty() && s.top() == a[i]) {
                    s.pop(); i ++;
                }
                if (j <= n) s.push(j++);
                else break;
            }
            if (i > n) puts("Yes");
            else puts("No");
        }
        cout << endl;
    }

    return 0;
}

例6-3 UVA 442 矩阵链乘 (p141, 用栈实现简单的表达式解析)

思路
这个题的输入表达式非常标准,两个矩阵用一对括号包起来。如下面所示:
A
B
C
(AA)
(AB)
(AC)
(A(BC))
((AB)C)
(((((DE)F)G)H)I)
(D(E(F(G(HI)))))
((D(EF))((GH)I))
所以写代码的时候不用考虑太多,按照这样的标准格式递归处理即可。
代码

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

typedef pair<int, int> P;

P p[26];
stack<P> st;

int main(void)
{
    int n;
    cin >> n;
    char ch[2];
    int x, y;
    for (int i = 0; i < n; i ++) {
        scanf("%s%d%d", ch, &x, &y);
        p[ch[0]-'A'] = P(x, y);
    }

    string s;
    while (cin >> s) {
        while (st.size()) st.pop();
        int res = 0;
        for (int i = 0; i < s.size(); i ++) {
            if (s[i] == ')') {
                P b = st.top(); st.pop();
                P a = st.top(); st.pop();
                if (a.second != b.first) {
                    res = -1; break;
                } else {
                    res += (a.first * a.second * b.second);
                    st.push(P(a.first, b.second));
                }
            } else if (s[i] != '('){
                st.push(p[s[i]-'A']);
            }
        }
        if (res == -1) puts("error");
        else printf("%d\n", res);
    }

    return 0;
}

例6-4 UVA 11988 破损的键盘 (p143, 链表)

思路
链表用数组来写更简单。
还有一个小技巧是头节点为数组的第一个元素,且头节点是虚拟节点,这样便于插入删除操作的处理。
代码

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

const int N = 100000;

char s[N+2];
int next[N+2];

int main(void)
{
    while (scanf("%s", s+1) != EOF) {
        int cur = 0, last = 0;
        next[0] = 0;
        for (int i = 1; s[i]; 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;
}

例6-5 UVA 12657 移动盒子 (p144, 双向链表)

思路
双向链表的操作要比单向链表复杂一倍,写的时候注意以下几点:
1、反转用一个全局变量表示即可,不需要做实际反转操作。
2、指令1 2 3有重复模块,可以用辅助函数实现精简代码。
代码

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

const int N = 100000;

int n, next[N+1][2];
int d = 0;

void init()
{
    for (int i = 0; i < n; i ++) {
        next[i][0] = i+1;
        next[i+1][1] = i;
    }
    next[n][0] = 0;
    next[0][1] = n;
    d = 0;
}

int lt(int a)
{
    return next[a][(d+1)&1];
}

int rt(int a)
{
    return next[a][d];
}

void set_lt(int a, int b)
{
    next[a][(d+1)&1] = b;
}

void set_rt(int a, int b)
{
    next[a][d] = b;;
}

void link(int a, int b)
{
    set_rt(a, b);
    set_lt(b, a);
}

void move_left(int a, int b)
{
    link(lt(a), rt(a));
    link(lt(b), a);
    link(a, b);
}

void move_right(int a, int b)
{
    link(lt(a), rt(a));
    link(a, rt(b));
    link(b, a);
}

void swap_pos(int a, int b)
{
    if (rt(a) == b)
        move_right(a, b);
    else if (lt(a) == b)
        move_left(a, b);
    else {
        int la = lt(a), ra = rt(a);
        link(lt(b), a);
        link(a, rt(b));
        link(la, b);
        link(b, ra);
    }
}

void print_array()
{
    int a = 0;
    while (a = rt(a))
        printf("%d ", a);
    printf("\n");
}

long long sum()
{   
    int a = 0;
    long long res = 0;
    while (a = rt(a)) {
        res += a;
        if (!(a = rt(a))) break;
    }
    return res;
}

int main(void)
{
    int t = 0;
    while (cin >> n) {
        init();
        int m;
        cin >> m;
        int a, b, c; 
        for (int i = 1; i <= m; i ++) {
            scanf("%d", &a);
            switch(a) {
                case 1:
                    scanf("%d%d", &b, &c);
                    move_left(b, c);
                    break; 
                case 2:
                    scanf("%d%d", &b, &c);
                    move_right(b, c);
                    break;
                case 3:
                    scanf("%d%d", &b, &c);
                    swap_pos(b, c);
                    break;
                case 4:
                    d = (d+1)%2;
                    break;
            }
        }
        //print_array();
        printf("Case %d: %lld\n", ++t, sum());
    }
    return 0;
}

例6-6 UVA 679 小球下落 (p148, 完全二叉树编号)

思路
开始模拟小球整个运动过程写的,果断超时了。
后来发现第I个小球的运动轨迹可以直接根据I求出来,详见书中解释。
代码

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

int main(void)
{
    int t;
    while (cin >> t && t >= 0) {
        while (t --) {
            int d, k;
            cin >> d >> k;
            int n = (1<<d);
            int r = 1;
            while (r < n/2) {
                if (k&1) { r = 2*r; k = k/2+1;}
                else { r = 2*r+1; k = k/2;}
            }
            printf("%d\n", r);
        }
    }

    return 0;
}

例6-7 UVA 122 树的层次遍历 (p150, 二叉树的动态创建于 BFS)

思路
两种方法:
1、宽度优先遍历。
2、二叉树递归遍历,每层的节点存入对应层的vector数组中。
下面的代码实现了方法1。
代码

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

struct Node {
    int k;
    Node *l, *r;
};

Node* prase(Node *root, int k, char s[300])
{
    if (!root) {
        root = (Node *)malloc(sizeof(Node));
        root->k = 0;
        root->l = root->r = NULL;
    }
    if (s[0] == 'L') root->l = prase(root->l, k, s+1);
    else if (s[0] == 'R') root->r = prase(root->r, k, s+1);
    else {
        if (root->k == 0) root->k = k;
        else root->k = -1;
    }
    return root;
}

bool bfs(Node *root, vector<int>& res)
{
    queue<Node*> q;
    q.push(root);
    while (q.size()) {
        Node *p = q.front();
        q.pop();
        if (p->k <= 0) return false;
        res.push_back(p->k);
        if (p->l) q.push(p->l);
        if (p->r) q.push(p->r);
    }
    return true;
}

int main(void)
{
    char str[300], str1[300];
    while (scanf("%s", str) != EOF) {
        Node *root = NULL;
        do {
            if (strcmp(str, "()") == 0) break;
            int k;
            sscanf(str, "(%d", &k);
            int len = 0;
            for (char *ch = strchr(str, ',')+1; ch < strchr(str, ')'); ch ++)
                str1[len++] = *ch;
            str1[len] = '\0';
            root = prase(root, k, str1);
        } while (scanf("%s", str));

        vector<int> res;
        if (bfs(root, res)) {
            for (int i = 0; i < res.size(); i ++)
                printf("%d%c", res[i], (i == res.size()-1) ? '\n' : ' ');
        } else
            puts("not complete");
    }

    return 0;
}

例6-8 UVA 548 树 (p155, 从中序和后续回复二叉树)

思路
后序遍历的第一个字符就是根,因此只需在中序遍历中找到它,就知道左右子树的中序和后序遍历了。这样可以先把二叉树构造出来,然后再执行一次递归遍历,找到最优解。
代码

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

const int N = 10000;

int lt[N+1], rt[N+1], sum[N+1];
int min_value, min_sum;

int rebuild(int n, int in[], int post[], int sum)
{
    if (n == 0) return 0;
    int root = post[n-1];
    sum += root;
    if (n == 1) {
        if (sum < min_sum || sum == min_sum && root < min_value) {
            min_value = root;
            min_sum = sum;
        }
        lt[root] = rt[root] = 0;
        return root;
    }
    int m;
    for (m = 0; m < n; m ++) {
        if (in[m] == root) break;
    }
    lt[root] = rebuild(m, in, post, sum);
    rt[root] = rebuild(n-m-1, in+m+1, post+m, sum);
    return root;
}

int main(void)
{
    int n;
    int in[N+1], post[N+1];
    string s;
    while (getline(cin, s)) {
        n = 0;
        stringstream stm(s);
        while (stm >> in[n]) n ++;
        for (int i = 0; i < n; i ++)
            cin >> post[i];
        getchar();

        min_value = min_sum = N*N;
        rebuild(n, in, post, 0);
        printf("%d\n", min_value);
    }

    return 0;
}

例6-9 UVA 839 天平 (p157, 二叉树的 DFS)

思路
这个题输入既然采用了递归的形式遍历,编一个递归过程处理输入比较自然。事实上,再输入的过程中就能够完成判断。
我这个题做了多余的建树操作,实际上没有必要。
代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
    
struct Node {
    int w, d;
    Node *lt, *rt;
    Node():w(0),d(0),lt(NULL),rt(NULL){}
};

bool res;

void input(Node *root)
{
    Node *l = root->lt = new Node();
    Node *r = root->rt = new Node();
    scanf("%d%d%d%d", &l->w, &l->d, &r->w, &r->d);
    if (!l->w) input(l);
    if (!r->w) input(r);
    if (!root->w) root->w = l->w + r->w;
    if (l->w * l->d != r->w * r->d)
        res = false;
}

void deltree(Node *root)
{
    if (!root) return;
    if (root->lt) deltree(root->lt);
    if (root->rt) deltree(root->rt);
    delete root;
}

int main(void)
{
    int kase;
    cin >> kase;
    for (int t = 0; t < kase; t ++) {
        if (t) puts("");
        res = true;
        Node *root = new Node();
        input(root);
        deltree(root);
        if (res) puts("YES");
        else puts("NO");
    }

    return 0;
}

例6-10 UVA 699 下落的树叶 (p159, 二叉树的 DFS)

思路
以int sum[2*SFT]表示各水平位置的值的和。根节点对应位置为SFT,每个位置为k的节点的左节点位置为k-1,右节点位置为k+1。
其实就是个递归,很简单。
代码

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

const int SFT = 100;

int sum[2*SFT];

struct Node {
    int k, sft;
    Node *lt, *rt;
    Node():k(0),lt(NULL),rt(NULL){}
};

Node* input(int sft)
{
    int k;
    scanf("%d", &k);
    if (k == -1) return NULL;
    Node *p = new Node();
    p->k = k;
    sum[sft] += k;
    p->lt = input(sft-1);
    p->rt = input(sft+1);
    return p;
}

void print_sum()
{
    int i;
    for (i = 0; i < 2*SFT; i ++) {
        if (sum[i]) { printf("%d", sum[i++]); break; }
    }
    while (sum[i]) {
        printf(" %d", sum[i++]);
    }
    printf("\n");
}

void deltree(Node *root)
{
    if (!root) return;
    if (root->lt) deltree(root->lt);
    if (root->rt) deltree(root->rt);
    delete root;
}

int main(void)
{
    int t = 0;
    while (true) {
        memset(sum, 0, sizeof(sum));
        Node *root = input(SFT);
        if (!root) break;
        printf("Case %d:\n", ++t);
        print_sum();
        printf("\n");
        deltree(root);
    }

    return 0;
}

例6-11 UVA 297 四分树 (p160)

思路
跟二叉树极其类似,都是基于递归思想来处理输入。
代码

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

void input(int x0, int y0, int x1, int y1, char area[32][32])
{
    char ch = getchar();
    if (ch == 'f') {
        for (int i = x0; i < x1; i ++)
            for (int j = y0; j < y1; j ++)
                area[i][j] = '1';
    } else if (ch == 'p') {
        int mx = (x0+x1) / 2;
        int my = (y0+y1) / 2;
        input(x0, my, mx, y1, area);
        input(x0, y0, mx, my, area);
        input(mx, y0, x1, my, area);
        input(mx, my, x1, y1, area);
    }
}

int cnt(char area[2][32][32])
{
    int res = 0;
    for (int i = 0; i < 32; i ++) {
        for (int j = 0; j < 32; j ++) {
            if (area[0][i][j] == '1' || area[1][i][j] == '1')
                res ++;
        }
    }
    return res;
}

int main(void)
{
    int t;
    cin >> t;
    while (t--) {
        char area[2][32][32];
        memset(area, '0', sizeof(area));
        for (int i = 0; i < 2; i ++) {
            getchar();
            input(0, 0, 32, 32, area[i]);
        }
        printf("There are %d black pixels.\n", cnt(area));
    }

    return 0;
}

例6-12 UVA 572 油田 (p162, 图的连通块 DFS)

思路
最基本的DFS,注意是八连通不是四连通。
代码

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

#define N 100

int n, m;
char lake[N][N+1];

void dfs(int x, int y)
{
    lake[x][y] = '*';
    for (int dx = -1; dx <= 1; dx ++) {
        for (int dy = -1; dy <= 1; dy ++) {
            if (!dx && !dy)
                continue;
            int nx = x + dx, ny = y + dy;
            if (0 <= nx && nx < n && 0 <= ny && ny < m
                    && lake[nx][ny] == '@')
                dfs(nx, ny);
        }
    }
}

void solve() {
    int res = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (lake[i][j] == '@') {
                dfs(i, j);
                res ++;
            }
        }
    }
    printf("%d\n", res);
}

int main(void)
{
    while (cin >> n >> m, n || m) {
        for (int i = 0; i < n; i++)
            cin >> lake[i];

        solve();
    }

    return 0;
}

例6-13 UVA 1103 古代象形符号 (p163, 图的连通块的应用)

思路
这个题是含有洞的连通块。注意到:6个符号不管怎么变形,其拓扑上分别有1 3 5 4 0 2个洞,可以根据这一点来判断。
那么如何判断有多少个洞呢?我的程序的处理思路是:
1、初始状态下,0表示白点,1表示黑点;
2、用DFS对不同的连通块进行编号,将1分别修改为2 3 4 …,不同的非零数字代表不同的连通块;
3、用DFS将与外周连通起来的为0的区域修改为1,这时候所有为0的区域代表不同的洞;
4、用DFS数洞,判断洞的归属可以基于其DFS寻找到的大于1的连通点的数字。
代码

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

#define N 200
#define M 50

const char code[] = "WAKJSD";

int n, m;
char table[N][M+1];
int type[N][4*M];
int num;
int area[N*M];

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

void dfs(int x, int y, int a, int b) //change a to b
{
    type[x][y] = b;
    for (int dx = -1; dx <= 1; dx ++) {
        for (int dy = -1; dy <= 1; dy ++) {
            if (!dx && !dy) continue;
            int nx = x + dx, ny = y + dy;
            if (legal(nx, ny) && type[nx][ny] == a)
                dfs(nx, ny, a, b);
        }
    }
}

void trans()
{
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            int x = table[i][j] - '0';
            if (isalpha(table[i][j]))
                x = table[i][j] - 'a' + 10;
            int r = 3;
            while (x) {
                type[i][4*j+r] = (x&1);
                r --;
                x >>= 1;
            }
        }
    }
}

int check_nb(int x, int y)//检查是否为0且邻居不小于2
{
    if (!legal(x, y) || type[x][y] != 0) return 0;
    for (int dx = -1; dx <= 1; dx ++) {
        for (int dy = -1; dy <= 1; dy ++) {
            if (!dx && !dy) continue;
            int nx = x + dx, ny = y + dy;
            if (legal(nx, ny) && type[nx][ny] >= 2)
                return type[nx][ny];
        }
    }
    return 0;
}

int main(void)
{
    int t = 0;
    while (cin >> n >> m, n || m) {
        for (int i = 0; i < n; i++)
            cin >> table[i];
        memset(type, 0, sizeof(type));
        trans();
        m *= 4;

        num = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (type[i][j] == 1) {
                    num ++;
                    dfs(i, j, 1, num+1);
                }
            }
        }   
                
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (!(i == 0 || i == n-1 || j == 0 || j == m-1)) continue;
                if (type[i][j] == 0) {
                    dfs(i, j, 0, 1);
                }
            }
        }

        memset(area, 0, sizeof(area));
        for (int i = 0; i < n; i++) { 
            for (int j = 0; j < m; j++) { 
                int nb = check_nb(i, j);
                if (nb >= 2) {
                    area[nb] ++; 
                    dfs(i, j, 0, nb);
                }
            }
        }

        printf("Case %d: ", ++t);
        char res[N*M];
        for (int i = 0; i < num; i++)
            res[i] = code[area[i+2]];
        res[num] = '\0';
        sort(res, res+num); 
        printf("%s\n", res);
    }   
        
    return 0;
}

例6-14 UVA 816 Abbott 的复仇 (p165, 图的连通块 BFS)

思路
本题与普通迷宫在本质上是一样的,区别在于“朝向”也起到了关键作用,所以要用一个三元组(r, c, dir)表示单个状态。
BFS处理过程详见代码。
代码

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

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

int dir[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
char towards[] = "SENW";
char turn[] = "RFL";

struct Node {
    int x, y, t;
    int d;
    bool turn[3];
    Node *pre;
};

Node map[N+1][N+1][4];
Node *bg = new Node(), *ed = new Node();
int tb = 0;

void init_map()
{
    for (int i = 1; i <= N; i ++) {
        for (int j = 1; j <= N; j ++) {
            for (int k = 0; k < 4; k ++) {
                map[i][j][k].x = i;
                map[i][j][k].y = j;
                map[i][j][k].t = k;
                map[i][j][k].d = INF;
                map[i][j][k].pre = NULL;
                for (int r = 0; r < 3; r ++)
                    map[i][j][k].turn[r] = 0;
            }
        }
    }
}

int trans_tow(char ch)
{
    return strchr(towards, ch) - towards;
}

int trans_turn(char ch)
{
    return strchr(turn, ch) - turn;
}

bool legal(int x, int y, int t)
{
    return x >= 1 && x <= N && y >= 1 && y <= N && map[x][y][t].d == INF;
}

Node* next(int x, int y, int t)
{
    int nx = x + dir[t][0];
    int ny = y + dir[t][1];
    if (legal(nx, ny, t))
        return &map[nx][ny][t];
    else
        return NULL;
}

bool BFS()
{
    Node *np = next(bg->x, bg->y, tb);
    if (!np) return false;
    np->pre = bg;
    np->d = 1;
    queue<Node*> que;
    que.push(np);

    while( que.size() ) {
        Node *p = que.front();
        que.pop();

        if (p->x == ed->x && p->y == ed->y) {
            ed = p;
            return true;
        }

        for (int i = 0; i < 3; i ++) {
            if (!p->turn[i]) continue;
            int t = (p->t+i+3)%4;
            np = next(p->x, p->y, t);
            if (!np) continue;
            np->pre = p;
            np->d = p->d + 1;
            que.push(np);
        }
    }
    return false; 
}

int main(void) 
{
    char name[21];
    while (~scanf("%s", name) && strcmp(name, "END")) {
        init_map();
        
        char s[20];
        int x, y, t;
        scanf("%d%d%s", &x, &y, s);
        bg = &map[x][y][0];
        tb = trans_tow(s[0]);
        scanf("%d%d", &x, &y);
        ed = &map[x][y][0];
        while (scanf("%d", &x) && x) {
            scanf("%d", &y);
            while (scanf("%s", s) && strcmp(s, "*")) {
                t = trans_tow(s[0]);
                for (int i = 1; s[i]; i ++)
                    map[x][y][t].turn[trans_turn(s[i])] = 1;
            }
        }
        
        printf("%s\n  ", name);
        if (!BFS()) 
            printf("No Solution Possible\n");
        else {
            vector<Node *> path;
            Node *p = ed;
            while (p != bg) {
                p = p->pre;
                path.push_back(p);
            }

            for (int i = path.size()-1; i >= 0; i --) {
                printf("(%d,%d)%s", path[i]->x, path[i]->y,
                        ((path.size()-i)%10) ? " " : "\n  ");
            }
            printf("(%d,%d)\n", ed->x, ed->y);
        }
    }

    return 0;
}

例6-15 UVA 10305 按任务排序 (p167, 拓扑排序)

思路
把每个变量看成一个点,“小于”关系看成有向边,则得到了一个有向图。这样,我们的任务实际上是把一个图的所有结点排序,使得每一条有向边(u, v)对应的u都排在v的前面。在图论中,这个问题称为拓扑排序(topological sort)。
不难发现:如果图中存在有向环,则不存在拓扑排序,反之则存在。不包含有向环的有向图称为有向无环图(Directed Acyclic Graph,DAG)。可以借助DFS完成拓扑排序:在访问完一个结点之后把它加到当前拓扑序的首部(想一想,为什么不是尾部)。
这里用到了一个c数组,c[u]=0表示从来没有访问过(从来没有调用过dfs(u));c[u]=1表示已经访问过,并且还递归访问过它的所有子孙(即dfs(u)曾被调用过,并已返回);c[u]=-1表示正在访问(即递归调用dfs(u)正在栈帧中,尚未返回)。
提示:可以用DFS求出有向无环图(DAG)的拓扑排序。如果排序失败,说明该有向图存在有向环,不是DAG。

拓扑排序实际上也可以用BFS来做,个人感觉BFS更容易理解。因为拓扑排序是访问完一个结点后加到排序的首部,而BFS则是尾部。
同时附上我写的BFS代码,已经AC。
代码1(基于DFS的拓扑排序)

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

const int N = 101;

int n, m;
bool G[N][N];
int c[N];
int topo[N], t;

void init()
{
    memset(G, 0, sizeof(G));
    memset(c, 0, sizeof(c));
    memset(topo, 0, sizeof(topo));
}

bool dfs(int u)
{
    c[u] = -1;
    for (int k = 1; k <= n; k++) {
        if (G[u][k]) {
            if (c[k] < 0) return false;
            else if (!c[k] && !dfs(k)) return false;
        }
    }

    c[u] = 1;
    topo[t--] = u;
    return true;
}

bool toposort()
{
    t = n;
    for (int u = 1; u <= n; u++) {
        if (!c[u]) {
            if (!dfs(u)) return false;
        }
    }
    return true;
}

int main(void)
{
    while (scanf("%d%d", &n, &m) != EOF , n || m) {
        init();
        int a, b;
        while (m--) {
            scanf("%d%d", &a, &b);
            G[a][b] = 1;
        }
        if (toposort()) {
            for (int i = 1; i <= n; i++)
                printf("%d%c", topo[i], i == n ? '\n' : ' ');
        }
    }

    return 0;
}

代码2(基于BFS的拓扑排序)

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

const int N = 101;

int n, m;
vector<int> G[N];
int pre[N];
vector<int> topo;

void init()
{
    for (int i = 1; i <= n; i++)
      G[i].clear();
    memset(pre, 0, sizeof(pre));
}

bool toposort()
{
    queue<int> q;
    for (int u = 1; u <= n; u++) {
        if (!pre[u]) q.push(u);
    }

    while (q.size()) {
        int u = q.front(); q.pop();
        topo.push_back(u);
        //printf("u = %d\n", u);
        for (int i = 0; i < G[u].size(); i++) {
            int k = G[u][i];
            pre[k]--;
            if (pre[k] == 0)
                q.push(k);
            else if (pre[k] < 0)
                return false;
        }
    }
    return topo.size() == n;
}

int main(void)
{
    while (scanf("%d%d", &n, &m) , n || m) {
        init();
        int a, b;
        while (m--) {
            scanf("%d%d", &a, &b);
            if (a != b) {
                pre[b]++;
                G[a].push_back(b);
            }
        }
        if (toposort()) {
            for (int i = 0; i < topo.size(); i++)
              printf("%d%c", topo[i], i == topo.size()-1 ? '\n' : ' ');
        }
    }

    return 0;
}

例6-16 UVA 10129 单词 (p169, 欧拉回路)

思路
有向图存在欧拉回路的必要条件:最多只能有两个点的入度不等于出度,而且必须是其中一个点的出度恰好比入度大1(把它作为起点),另一个的入度比出度大1(把它作为终点)。当然,还有一个前提条件:在忽略边的方向后,图必须是连通的。

对于这个题,书中的解释是:把字母看作结点,单词看成有向边,则问题有解,当且仅当图中有欧拉路径。前面讲过,有向图存在欧拉道路的条件有两个:底图(忽略边方向后得到的无向图)连通,且度数满足上面讨论过的条件。判断连通的方法有两种,一是之前介绍过的DFS,二是第11章中将要介绍的并查集。读者可以在学习完并查集之后根据自己的喜好选用。

刘汝佳所提供的源码用的是并查集。但他同时提到用DFS也能做,于是我就想怎么用DFS来做。标准DFS过程每次搜索所有邻接节点,访问过的节点用一个数组标记。而如果DFS过程中不删掉已经访问过的边,则没遇到一个新节点都需要搜索其邻接的所有节点,岂不是多了很多无用的判断?但如果删掉访问过的边,DFS就必须从起点开始进行。
因此我的程序中,先判断节点的入度和出度是否满足条件,同时找到出度与入度之差为1的节点作为起点(如果找不到则将任意有出度的节点作为起点)。从起点进行DFS,如果访问完毕所有边都被删掉了,则说明图是连通的。

另外有几个小技巧可以提高效率:
1、读入的单词只需要将其存在vector end[26]中,见主程序中:int x = s[0]-‘a’, y = s[s.size()-1]-‘a’; end[x].push_back(y); 意思是存在一个单词,以数字x(与字符’a’之差)为首字母,数字y为末字母。这样大大降低了存储。
2、DFS访问过程中,删除的边应该从vector的末端进行。

代码

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

const int N = 100000;

int n;
vector<int> end[26];
int in[26], out[26];

void init_data()
{
    for (int i = 0; i < 26; i++)
        end[i].clear();
    memset(in, 0, sizeof(in));
    memset(out, 0, sizeof(out));
}

void dfs(int u)
{
    while(end[u].size()) {
        int len = end[u].size();
        int k = end[u][len-1];
        end[u].resize(len-1);
        dfs(k);
    }
}

bool solve()
{
    //以下判断有向图的其中一个必要条件:
    //最多只能有两个点的入度不等于出度,
    //而且必须是其中一个点的出度恰好比入度大1,另一个小1
    vector<int> deg;
    int bg;
    for (int i = 0; i < 26; i++) {
        if (in[i] || out[i]) bg = i;
        if (in[i] != out[i]) deg.push_back(i);
    }
    if (deg.size() == 1 || deg.size() > 2) return false;
    if (deg.size() == 2) {
        int i = deg[0];
        if (abs(in[i]-out[i]) != 1) return false;
        bg = (out[i] - in[i] == 1) ? i : deg[1];
    }

    //以下判断有向图欧拉路径的第二个必要条件:
    //图是连通的。
    dfs(bg);
    for (int i = 0; i < 26; i++)
        if (end[i].size()) return false;
    return true;
}

int main(void)
{
    int kase;
    scanf("%d", &kase);
    while (kase--) {
        scanf("%d", &n);
        init_data();
        string s;
        for (int i = 0; i < n; i++) {
            cin >> s;
            int x = s[0]-'a', y = s[s.size()-1]-'a';
            end[x].push_back(y);
            in[y]++;
            out[x]++;
        }

        if (solve()) printf("Ordering is possible.\n");
        else printf("The door cannot be opened.\n");
    }

    return 0;
}

例6-17 UVA 10562 看图写数 (p170, 多叉数 DFS)

思路
其实就是递归遍历树,递归多写几遍掌握了这种题就很容易。另外根本不需要建树,直接递归输出就行。
WA了一次,认真看了一下原题发现还需要处理空树的情况,确实漏掉了。
代码

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

const int N = 200;

int n;
char dat[N+1][N+1];

void prase(int x, int y0, int y1)
{
    for (int y = y0; y <= y1; y ++) {
        if (y >= 0 && y < strlen(dat[x]) && dat[x][y] != ' ') {
            printf("%c(", dat[x][y]);
            if (x < n-1 && dat[x+1][y] == '|') {
                int m0 = y, m1 = y;
                while (m0 >= 0 && dat[x+2][m0] == '-') m0 --;
                while (dat[x+2][m1] == '-') m1 ++;
                prase(x+3, m0+1, m1-1);
            }
            printf(")");
        }
    }
}

int main(void)
{
    int t;
    cin >> t;
    getchar();
    while (t --) {
        n = 0;
        while (gets(dat[n]) && strcmp(dat[n], "#"))
            n ++;
        printf("(");
        if (n) prase(0, 0, strlen(dat[0])-1);
        printf(")\n");
    }

    return 0;
}

例6-18 UVA 12171 雕塑 (p171, 离散化: floodfill)(未尝试)

思路

代码



例6-19 UVA 1572 自组合 (p172, 图论模型)

思路
待补充。
代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
        
const int N = 52;
            
bool G[N][N];
int c[N];   
                
void init()     
{                   
    memset(G, 0, sizeof(G));
    memset(c, 0, sizeof(c));
}           
            
bool dfs(int u) 
{                   
    c[u] = -1;          
    for (int k = 0; k < N; k++) {
        if (G[u][k]) {
            if (c[k] < 0) return false; //存在有向环
            else if (!c[k] && !dfs(k)) return false;
        }
    }
        
    c[u] = 1;
    return true;
}
    
bool toposort()
{
    for (int u = 0; u < N; u++) {
        if (!c[u]) {
            if (!dfs(u)) return false;
        }
    }
    return true;
}

int main(void)
{
    int m;
    while (scanf("%d", &m) != EOF) {
        init();
        char s[10];
        while (m--) {
            scanf("%s", s);
            int a[4];
            for (int i = 0; i < 4; i++) {
                a[i] = N;
                if (isalpha(s[2*i])) {
                    a[i] = s[2*i]-'A';
                    if (s[2*i+1] == '+') a[i] += 26;
                }
            }
            for (int i = 0; i < 4; i++) {
                for (int j = 0; j < 4; j++) {
                    if (i != j && a[i] < N && a[j] < N) {
                        G[a[i]][(a[j]+26)%N] = 1;
                        //printf("%d %d\n", a[i], a[j]);
                    }
                }
            }
        }

        if (toposort()) printf("bounded\n");
        else printf("unbounded\n");
    }

    return 0;
}

例6-20 UVA 1599 理想路径 (p173, 图的 BFS 树)(未尝试)

思路

代码



例6-21 UVA 506 系统依赖 (p173, 图的概念与拓扑排序)(未尝试)

思路

代码



例6-22 UVA 11853 战场 (p175, 对偶图)(未尝试)

思路

代码



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值