文章目录

  • 说明
  • 习题
    • 习6-1 UVA 673 平衡的括号
    • 习6-2 UVA 712 S - 树
    • 习6-3 UVA 536 二叉树重建
    • 习6-4 UVA 439 骑士的移动
    • 习6-5 UVA 1600 巡逻机器人
    • 习6-6 UVA 12166 修改天平(未尝试)
    • 习6-7 UVA 804 Petri 网模拟
    • 习6-8 UVA 806 空间结构
    • 习6-9 UVA 127 纸牌游戏
    • 习6-10 UVA 246 10-20-30
    • 习6-11 UVA 10410 树重建
    • 习6-12 UVA 810 筛子难题
    • 习6-13 UVA 215 电子表格计算器
    • 习6-14 UVA 12118 检察员的难题(未尝试)

说明

本文是我对第六章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;} elsest.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;} elsefirst = 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) { //downnt = 7-f; nf = t;} else if (i == 1) { //rightnt = 7-r[t][f]; nf = f;} else if (i == 2) { //upnt = f; nf = 7-t;} else if (i == 3) { //leftnt = 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-A00     1     2     3
A    10    10    10    10
B    10     4     6     2
C     1     2     3     4
D   -29     8     5     6A0: A1
A1: A2
A2: B0
B0: B1
B1: B2
B2: C0
C0: C1
C1: C2
C2: A00     1     2
A     6     6     6
B     6     6     6
C     6     6     6A3: C2
A4: C4+C5+C2
C2: A2+B6-C0+E5+5
E5: A30     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'));elseval[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 检察员的难题(未尝试)

思路

代码



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

  1. 算法竞赛入门经典第二版 刘汝佳

    链接:https://pan.baidu.com/s/1E8wszcAB2d8bdS7TMxKP8g 提取码:89pc 复制这段内容后打开百度网盘手机App,操作更方便哦

  2. UVA-12171 雕塑 题解答案代码 算法竞赛入门经典第二版

    GitHub - jzplp/aoapc-UVA-Answer: 算法竞赛入门经典 例题和习题答案 刘汝佳 第二版 这道题目在<算法竞赛入门经典第二版>书中标注了星号,也是第一道出现星号的 ...

  3. 补学图论算法:算法竞赛入门经典(第二版)第十一章:

    补学图论算法:算法竞赛入门经典(第二版)第十一章: 倒排索引还没有实现! 下面是左神的图论算法,并查集笔记.和一个美团题目. ''' https://www.nowcoder.com/live/11? ...

  4. 算法竞赛入门经典第二版课后习题答案第二章

    算法竞赛入门经典第二版课后习题答案 第二章 习题2-1水仙花数 输出100-999中的所有水仙花数.若三位数ABC满足ABC=A^3+B^3+C^3,则称其为水仙花数.例如153=1^3+5^3+3^ ...

  5. UVA-814 邮件传输代理的交互 题解答案代码 算法竞赛入门经典第二版

    GitHub - jzplp/aoapc-UVA-Answer: 算法竞赛入门经典 例题和习题答案 刘汝佳 第二版 AC代码 #include<iostream> #include< ...

  6. UVA-1598 交易所 题解答案代码 算法竞赛入门经典第二版

    GitHub - jzplp/aoapc-UVA-Answer: 算法竞赛入门经典 例题和习题答案 刘汝佳 第二版 AC代码 有意思的一个题目.书上说这是一个不错的优先队列练习题,但实际上它其实是一个 ...

  7. 算法竞赛入门经典第二版:循环结构程序设计实例与习题

    实例: 1.阶乘之和 输入n,计算S= 1!+2!+3!+-+n!的末六位. 分析:两个循环,里面循环用于计算不同数的阶乘,外面一个循环用于将所有阶乘相加,核心算法 "for(int i=1 ...

  8. UVA - 1225 Digit Counting(刘汝佳紫书题单(算法竞赛入门经典 第二版 2014)

    个人感觉这道题有表述问题 他的题目表述说明测试数据中的数是不超过20的,但其实他的测试点中是有百位数的,所以按两位数去处理数据显然会造成数组越界. 代码如下 #include <bits/std ...

  9. 算法竞赛入门经典(第二版) | 例题4-5 追踪电子表格中的单元格 (UVa512,Spreadsheet Tracking,World Finals)(解法二)

    本着清晰明了易懂可以水两篇 的理念,笔者将这道题分两次发布.这是第二种解法. 第一种解法传送门→解法一+提交网址 因为解法1中有详细关于题目和输入输出格式等的介绍,这里就不过多赘述了. 分析: 一些初 ...

最新文章

  1. 优秀Java程序员应该知道的20个实用开源库
  2. 我的zsh配置, 2019最新方案
  3. Javascript中最常用的61段经典代码
  4. 牛客网_PAT乙级_1023旧键盘打字(20)【别人代码里用到的hash是啥】
  5. P5025-[SNOI2017]炸弹【tarjan,线段树优化建图】
  6. php 解压rar文件怎么打开方式,php 解压rar文件
  7. JVM系列(三)— Java内存模型
  8. redis安装包_redis安装与调优部署文档(WinServer)
  9. Linux常用指令---快捷键
  10. 推荐系统如何一键实现工业级部署? ElasticCTR 百度开讲
  11. flutter initializing gradle终极解决方案
  12. 【iOS-Cocos2d游戏开发之十五】详解CCProgressTimer 进度条实现“理想”游戏进度条!...
  13. [译] 实例解析 ES6 Proxy 使用场景
  14. 【2018宁夏邀请赛 L】Continuous Intervals【线段树】
  15. Python OpenCV实现身份证号码识别
  16. 有专门收C语言答案的软件吗,C语言二级考试题库APP
  17. 简单的Charles抓包ios微信网页
  18. 宋朝人物第一,朱熹都说他是“天地间第一流人物”
  19. 拼音表大全图_一年级语文26个汉语拼音字母表读法+写法+笔顺(附视频)
  20. python 模拟登录超星强智系统

热门文章

  1. 学会这一招,轻松玩转小程序自动化
  2. 解决from tensorflow.contrib.data import Dataset ImportError: cannot import name 'Dataset'
  3. Windows资源监视器软件的原理
  4. STI、LOD与WPE概念2:减少或避免WPE/STI效应对IP模块设计的影响
  5. Helvetica字体的50年
  6. 雷击计算机网络,计算机网络系统防雷设计方案
  7. 中兴 面试2020-09-21
  8. 计算机机房空调原理,精密空调/机房专用空调双系统机组工作原理
  9. 【持续更新】Ubuntu工具——vscode
  10. 【中间件-keycloak】第一次改开源中间件keycloak总个结