【序】
第6章一共有22个例题,这些例题大多十分经典,在教材和题解的帮助下,已经全部通过了书上的例题。现在就把我做题过程总结一下。
【正文】
题目传送门:点击打开链接
由于这些题都在书上有详细讲解,然后题意也有中文翻译,我就不写题意了,可以翻看紫书,了解题意。
【6-1】 模拟并行程序处理。STL队列 双端队列 的应用 用双端队列维护即将执行的程序 再用个队列维护等待变量释放的程序 用lock表示变量锁定状态先将所有程序依次放到执行队列中 每次取出队首程序运行不超过lim时间 未运行完又放到执行队列队尾 。遇到lock时 若当前锁定状态为false就将锁定状态变为true 否则将当前程序放到等待队列队尾并结束运行 ,遇到unlock时 若等待队列有程序 就将等待队列队首程序放到执行队列队首,遇到end时 退出当前执行(不再进队尾)。
bool lock;
deque <int> q1;
queue <int> q2; // q1代表等待队列,q2代表阻止队列
vector <string> v[maxn];
int Q, t[maxn], var[maxn], cnt[maxn];
void operate(int i)
{
int x;
int time = Q;
string cur;
while(time > 0)
{
cur = v[i][cnt[i]];
if(cur[2] == '=')
{
time -= t[0];
x = cur[4] - '0';
if(cur.size() == 6){
x = x * 10 + (cur[5] - '0');
}
var[cur[0] - 'a'] = x;
}
else if(cur[2] == 'i'){
time -= t[1];
printf("%d: %d\n", i, var[cur[6]-'a']);
}
else if(cur[2] == 'c'){
time -= t[2];
if(lock){
q2.push(i);
return ;
}
else{
lock = 1;
}
}
else if(cur[2] == 'l'){
time -= t[3];
lock = 0;
if(!q2.empty()){
x = q2.front(); q2.pop();
q1.push_front(x);
}
}
else{
return ;
}
++cnt[i];
}
q1.push_back(i);
}
int main()
{
int T, n;
scanf("%d", &T);
while(T--){
scanf("%d", &n);
for(int i = 0; i < 5; i++) scanf("%d", &t[i]);
scanf("%d", &Q);
memset(var, 0, sizeof(var));
memset(cnt, 0, sizeof(cnt));
while(!q1.empty()) q1.pop_back();
while(!q2.empty()) q2.pop();
string input;
for(int i = 1; i <= n; i++){
v[i].clear();
while(getline(cin, input)){
if(input == "") continue;
//if(input == "end") break;
v[i].push_back(input);
if(v[i].back() == "end") break;
}
q1.push_back(i);
}
while(q1.size()){
int u = q1.front(); q1.pop_front();
operate(u);
}
if(T){
printf("\n");
}
}
return 0;
}
【6-2】一个很经典的利用栈的问题了,在中转站C中,车厢符合后进先出的原则所以是一个栈,然后模拟这个过程就行了。书上的代码是不能在OJAC的,所以提供一份AC代码。
int n, a[maxn];
int main()
{
while(cin>>n && n)
{
//while(!s.empty()) s.pop();
while(cin>>a[1] && a[1])
{
stack <int> s;
for(int i = 2; i <= n; i++) cin>>a[i];
int l = 1, r = 1, ok = 1;
while(r <= n)
{
if(l == a[r])
{
l++, r++;
}
else if(!s.empty() && s.top() == a[r])
{
s.pop();
r++;
}
else if(l <= n) s.push(l++);
else
{
ok = 0;
break;
}
}
cout<<(ok ? "Yes" : "No")<<endl;
}
cout<<endl;
}
return 0;
}
【6-3】给出n个矩阵的维度和一些矩形链乘的表达式,要输出乘法次数。本题就是解析表达式,本题的表达很简单,可以用栈来完成:遇到字母时入栈,遇到右括号出栈并计算,然后结果入栈。AC代码见书上142页。
【6-4】单链表的简单应用。每输入一个字符就把它存起来,设输入的字符串是s[1-n]则可以用nxt【i】表示当前显示屏中s[i]右边的字符编号(即在s中的下标)。为了方便起见,假设字符串前面还有一个虚拟的s[0],nxt【0】可以表示显示屏中最左的字符再用一个变量cur表示光标的位置:即当前光标位于s[cur]的右边。cur = 0说明光标位于虚拟字符 s[0]的右边,即是显示频的最左边,为了移动光标,还需要用一个last表示显示频的最后一个字符是s[last]。如果对这个过程不是很理解,可以在纸上模拟一下,做和链表相关的题,在纸上模拟一下还是很有必要的。
const int maxn = 100000 + 5;
int nex[maxn], cur, last; // 光标在cur的后面
char s[maxn];
int main()
{
while(scanf("%s", s + 1) != EOF)
{
int n = strlen(s + 1);
last = cur = 0;
nex[0] = 0;
for(int i = 1; i <= n; i++){
char ch = s[i];
if(ch == '[') cur = 0;
else if(ch == ']') cur = last;
else{
nex[i] = nex[cur];
nex[cur] = i;
if(cur == last) last = i; //更新最后一个字符编号
cur = i;
}
}
for(int i = nex[0]; i != 0; i = nex[i]){
printf("%c", s[i]);
}
printf("\n");
}
return 0;
}
【6-5】双向链表的应用,用left[i]和right[i]分别表示编号i的盒子左边和右边的盒子编号(如果是0,表示不存在)则链接代码为
int Left[maxn], Right[maxn];
void Link(int L, int R){ //双链表连接方式
Right[L] = R; Left[R] = L;
}
有了这个代码之后,可以先记录好操作之前X和Y两边的节点,然后用link函数按照某种顺序把他们链接起来。操作4很特殊,我们可以增加一个标记inv表示有没有执行过这种操作,这样当op为1和2且inv=1的时候,只需要把op变成3-op即可。最后输出的时候根据inv的值不同处理就好。
const int maxn = 100010;
int Left[maxn], Right[maxn];
void Link(int L, int R){ //双链表连接方式
Right[L] = R; Left[R] = L;
}
int inv;
int main()
{
int n, m, ks = 0;
while(scanf("%d%d", &n, &m) != EOF)
{
//预处理双链表连接
for(int i = 1; i <= n; i++){
Left[i] = i - 1;
Right[i] = (i + 1) % (n + 1);
}
Right[0] = 1; Left[0] = n;
//处理查询
int op, X, Y, inv = 0;
while(m--)
{
scanf("%d", &op);
if(op == 4) inv ^= 1;
else{
scanf("%d%d", &X, &Y);
if(op == 3 && Right[Y] == X) swap(X, Y);
if(op != 3 && inv) op = 3 - op;
if(op == 1 && X == Left[Y]) continue;
if(op == 2 && X == Right[Y]) continue;
int LX = Left[X], RX = Right[X];
int LY = Left[Y], RY = Right[Y];
if(op == 1){
Link(LX, RX);
Link(LY, X);
Link(X, Y);
}
else if(op == 2){
Link(LX, RX);
Link(Y, X);
Link(X, RY);
}
else if(op == 3){
if(Right[X] == Y){
Link(LX, Y);
Link(Y, X);
Link(X, RY);
}
else{
Link(LX, Y);
Link(Y, RX);
Link(LY, X);
Link(X, RY);
}
}
}
}
int b = 0;
long long ans = 0;
for(int i = 1; i <= n; i++){
b = Right[b];
if(i % 2 == 1) ans += b;
}
if(inv && n % 2 == 0){
ans = (long long) n * (n + 1) / 2 - ans;
}
printf("Case %d: %lld\n", ++ks, ans);
}
return 0;
}
【6-6】该题可以用数组模拟的方法来做,但是这样做的话如果二叉树的深度比较大的话,就需要一个极大的数组来保存数据。因此用数组模拟是不可取的。
每个小球都会落在根结点上,因此前两个小球必然是一个在左子树,一个在右子树。所以只需要看小球编号的奇偶性,就能知道它最终在哪棵子树中。对于那些落入根结点左子树的小球来说,只需要知道该小球是第几个落在根的左子树里的,就可以知道它下一步是往左还是往右了。依次类推,直到小球落到叶子上。
如果使用题目中给出的编号I,则当I是奇数时,它是往左走的第(I+1)/2个小球;当I是偶数时,它是往右走的第I/2个小球。这样,可以直接模拟最后一个小球的路线:
int k = 1;
for(int i = 0; i < D - 1; i++){
if(I % 2){
k = k * 2;
I = (I + 1) / 2;
}
else{
k = k * 2 + 1;
I = I / 2;
}
}
printf("%d\n", k);
【6-7】树的层次遍历,这个题需要动态结构,有两种版本,一种是数组版本,一种是指针版本。指针版本记得释放空间。我只写了指针版本,数组版本只需要稍加魔改一下就可以了。
bool failed;
struct Node{
bool have_value;
int v;
Node *left, *right;
Node():have_value(false), left(NULL), right(NULL){}
};
Node *root;
Node* newnode(){return new Node();}
void addnode(int v, char *s)
{
int n = strlen(s);
Node *u = root;
for(int i = 0; i < n; i++){
if(s[i] == 'L'){
if(u->left == NULL) u->left = newnode();
u = u->left;
}
else if(s[i] == 'R'){
if(u->right == NULL) u->right = newnode();
u = u->right;
}
}
if(u->have_value) failed = true;
u->v = v;
u->have_value = true;
}
void remove_tree(Node *u)
{
if(u == NULL) return;
remove_tree(u->left);
remove_tree(u->right);
delete u;
}
char s[maxn];
bool read_input(){
failed = false;
remove_tree(root);
root = newnode();
for(;;){
if(scanf("%s", s) != 1) return false;
if(!strcmp(s, "()")) break;
int v;
sscanf(&s[1], "%d", &v);
addnode(v, strchr(s, ',') + 1);
}
return true;
}
bool bfs(vector <int> &ans)
{
queue <Node*> q;
ans.clear();
q.push(root);
while(!q.empty()){
Node *u = q.front(); q.pop();
if(!u->have_value) return false;
ans.push_back(u->v);
if(u->left != NULL) q.push(u->left);
if(u->right != NULL) q.push(u->right);
}
return true;
}
int main()
{
vector <int> ans;
while(read_input()){
if(!bfs(ans)){
failed = 1;
}
if(failed){
printf("not complete\n");
}
else{
for(int i = 0; i < ans.size(); i++){
printf("%d%c",ans[i],i==(int)(ans.size() - 1) ? '\n' : ' ');
}
}
}
return 0;
}
【6-8】 后序遍历的最后一个字符就是根,因此只需要在中序遍历里面找到它,就知道左右子树的中序和后序遍历了。这样可以先把二叉树构造出来,然后在执行一次递归遍历找到最优解。
const int maxv = 10010;
int in[maxv], post[maxv], lc[maxv], rc[maxv];
int n;
bool input(int *a){
string line;
if(!getline(cin, line)) return false;
stringstream ss(line);
n = 0;
int x;
while(ss >> x) a[n++] = x;
return n > 0;
}
int build(int L1, int R1, int L2, int R2){
if(L1 > R1) return 0;
int root = post[R2];
int p = L1;
while(in[p] != root) p++;
int cnt = p - L1;
lc[root] = build(L1, p-1, L2, L2 + cnt - 1);
rc[root] = build(p + 1, R1, L2 + cnt, R2 - 1);
return root;
}
int best, best_sum;
void dfs(int u, int sum)
{
sum += u;
if(!lc[u] && !rc[u]){
if(sum < best_sum || (sum == best_sum && u < best)){
best = u;
best_sum = sum;
}
}
if(lc[u]) dfs(lc[u], sum);
if(rc[u]) dfs(rc[u], sum);
}
int main()
{
while(input(in)){
input(post);
build(0, n - 1, 0, n - 1);
best_sum = 1e9;
dfs(post[n-1], 0);
printf("%d\n", best);
}
return 0;
}
【6-9】这道题目的输入就采取了递归定义的方式,因此编写一个递归的过程输入很自然。然后紫书上提供了一种很惊艳的做法,十分简便。使用引用传值,值得学习。
//代码功能为,输入一个子天平,返回天平是否平衡,参数W修改为天平的总重量
bool solve(int &W){
int W1, D1, W2, D2;
bool b1 = true, b2 = true;
cin >> W1 >> D1 >> W2 >> D2;
if(!W1) b1 = solve(W1);
if(!W2) b2 = solve(W2);
W = W1 + W2;
return b1 && b2 && (W1 * D1 == W2 * D2);
}
【6-10】 此题根本不需要构建二叉树,关键是建了也没用。因为题目要求每一列的总和。只需用一个一维数组保存每一列的和就可以了。如int sum[100000]即可。根的水平位置可设为中间即可:root=100000/2就行了。这时调用dfs(root);搞定!
int sum[maxn*3];
void build(int p)
{
int v; cin>>v;
if(v == -1) return;
sum[p] += v;
build(p - 1);
build(p + 1);
}
bool input()
{
int v; cin>>v;
if(v == -1) return false;
memset(sum, 0, sizeof(sum));
int pos = maxn / 2;
sum[pos] = v;
build(pos - 1);
build(pos + 1);
}
int main()
{
int ks = 0;
while(input())
{
int p = 0;
while(sum[p] == 0) p++;
printf("Case %d:\n", ++ks);
printf("%d", sum[p++]);
while(sum[p] != 0) printf(" %d", sum[p++]);
printf("\n\n");
}
return 0;
}
【6-11】4分树。给出两颗四分树的先序遍历,求二者合并之后的黑色像素的个数。p表示中间节点,f表示黑色, e表示白色(empty)。由于4分树比较特殊,只给出先序遍历就可以确定整颗树。只需要写一个画出来的过程,边画边统计就好。代码如下:
const int len = 32;
char s[maxn];
int buf[len][len], cnt;
void draw(const char *s, int &p, int r, int c, int w)
{
char ch = s[p++];
if(ch == 'p'){
draw(s, p, r, c + w / 2, w / 2); //1
draw(s, p, r, c, w / 2); //2
draw(s, p, r + w / 2, c, w / 2); //3
draw(s, p, r + w / 2, c + w / 2, w / 2);
}
else if(ch == 'f'){
for(int i = r; i < r + w; i++){
for(int j = c; j < c + w; j++){
if(buf[i][j] == 0){
buf[i][j] = 1;
cnt++;
}
}
}
}
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
memset(buf, 0, sizeof(buf));
cnt = 0;
for(int i = 0; i < 2; i++){
scanf("%s", s);
int p = 0;
draw(s, p, 0, 0, len);
}
printf("There are %d black pixels.\n", cnt);
}
return 0;
}
【6-12】DFS求连通块,这个算法还有个名字叫做(floodfill),以后就这样命名了,就是DFS给连通块标号。然后求出连通块个数。
char pic[maxn][maxn];
int m, n, idx[maxn][maxn]; //联通分量编号
void dfs(int r, int c, int id)
{
if(r < 0 || r >= m || c < 0 || c >= n) return;
if(idx[r][c] > 0 || pic[r][c] != '@') return;
idx[r][c] = id;
for(int dr = -1; dr <= 1; dr++)
for(int dc = -1; dc <= 1; dc++)
if(dr != 0 || dc != 0) dfs(r + dr, c + dc, id);
}
int main()
{
while(scanf("%d%d", &m, &n) && m && n)
{
for(int i = 0; i < m; i++) scanf("%s", pic[i]);
memset(idx, 0, sizeof(idx));
int cnt = 0;
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(idx[i][j] == 0 && pic[i][j] == '@') dfs(i, j, ++cnt);
}
}
printf("%d\n", cnt);
}
return 0;
}
【6-13】给了一个H*W的字符矩阵,每个字符为4个相邻像素点的16进制表示(例如,10011100对应字符为9c)。转化为二进制后1表示黑点,0表示白点。这个题的关键点是如何来辨认这6种不同的文字,我们可以发现每种文字中间的空白圈数是不同的,我们就利用这个特征值来辨认。我们先给迷宫外层加一圈空白(0),这个技巧十分重要,可以看出之后许多图论题都采用了这个技巧。然后从(0,0)开始执行一次floodfill把周围所有的空白全部用‘’@符号填充,接下来我们统计每一个连通块里面0的个数,就知道当前所在的连通块对应的文字是什么了。
int dir[4][2] = {{0, 1}, {0, -1}, {-1, 0}, {1, 0}};
string code1[16] = {"0000", "0001", "0010", "0011",
"0100", "0101", "0110", "0111",
"1000", "1001", "1010", "1011",
"1100", "1101", "1110", "1111"};
char code2[8] = {'W', 'A', 'K', 'J', 'S', 'D'};
map <char, int> mp;
char maze[256][256];
int H, W, ks, cnt;
bool check(int x, int y)
{
if(x >= 0 && x <= H + 1 && y >= 0 && y <= W + 1){
return true;
}
else{
return false;
}
}
void dfs1(int r, int c)
{
if(!check(r, c) || maze[r][c] != '0') return;
maze[r][c] = '@';
for(int i = 0; i < 4; i++){
int dx = r + dir[i][0];
int dy = c + dir[i][1];
dfs1(dx, dy);
}
}
void dfs2(int r, int c)
{
if(!check(r, c) || maze[r][c] != '1') return;
maze[r][c] = '@';
for(int i = 0; i < 4; i++){
int dx = r + dir[i][0];
int dy = c + dir[i][1];
if(maze[dx][dy] == '0'){
++cnt;
dfs1(dx, dy);
}
else{
dfs2(dx, dy);
}
}
}
int main()
{
while(scanf("%d%d", &H, &W) != EOF)
{
if(H == 0 && W == 0) break;
W = W * 4;
memset(maze, '0', sizeof(maze));
mp.clear();
for(int i = 1; i <= H; i++){
string line, tmp;
cin >> line;
for(auto i : line) tmp += code1[i >= 'a' ? (i - 'a' + 10) : (i - '0')];
memcpy(maze[i] + 1, tmp.c_str(), W);
}
// for(int i = 1; i <= H; i++){
// for(int j = 1; j <= W; j++){
// printf("%c", maze[i][j]);
// }
// printf("\n");
// }
// printf("***************************************\n");
dfs1(0, 0);
// for(int i = 1; i <= H; i++){
// for(int j = 1; j <= W; j++){
// printf("%c", maze[i][j]);
// }
// printf("\n");
// }
// printf("***************************************\n");
for(int i = 1; i <= H; i++){
for(int j = 1; j <= W; j++){
if(maze[i][j] != '1') continue;
cnt = 0;
dfs2(i, j);
mp[code2[cnt]]++;
}
}
printf("Case %d: ", ++ks);
for(auto it : mp)
{
while(it.second--){
printf("%c", it.first);
}
}
printf("\n");
}
return 0;
}
【6-14】有一个最多包含9*9个交叉点的迷宫。输入起点、离开起点时的朝向和终点,求一条最短路(多解时任意输出一个即可)。其实就是bfs,但是难点在于转向,将四个方向和3种转弯方式编号为0~3和0~2。
下面是转弯函数的实现
const char *dirs = "NESW";
const char *turns = "FLR";
int dir_id(char c)
{
return strchr(dirs, c) - dirs;
}
int turn_id(char c)
{
return strchr(turns, c) - turns;
}
bool check(int x, int y)
{
if(x >= 1 && x <= 9 && y >= 1 && y <= 9) return true;
else return false;
}
const int dr[] = {-1, 0, 1, 0};
const int dc[] = {0, 1, 0, -1};
Node walk(const Node &u, int turn){
int dir = u.dir;
if(turn == 1) dir = (dir + 3) % 4; //逆时针
if(turn == 2) dir = (dir + 1) % 4; //顺时针
return Node(u.r + dr[dir], u.c + dc[dir], dir);
}
输入的时候读取交点的信息要注意处理。
利用has_edge[r][c][dir][turn]标记在坐标(r,c)处朝向dir转向turn能否成功。
const int maxn = 10;
struct Node{
int r, c, dir;
Node(int r = 0, int c = 0, int dir = 0) : r(r), c(c), dir(dir) {}
};
int has_edge[maxn][maxn][4][3];
int d[maxn][maxn][4];
Node p[maxn][maxn][4];
int r0, c0, dir, r1, c1, r2, c2;
const char *dirs = "NESW";
const char *turns = "FLR";
int dir_id(char c)
{
return strchr(dirs, c) - dirs;
}
int turn_id(char c)
{
return strchr(turns, c) - turns;
}
bool check(int x, int y)
{
if(x >= 1 && x <= 9 && y >= 1 && y <= 9) return true;
else return false;
}
const int dr[] = {-1, 0, 1, 0};
const int dc[] = {0, 1, 0, -1};
Node walk(const Node &u, int turn){
int dir = u.dir;
if(turn == 1) dir = (dir + 3) % 4; //逆时针
if(turn == 2) dir = (dir + 1) % 4; //顺时针
return Node(u.r + dr[dir], u.c + dc[dir], dir);
}
void print_ans(Node u){
vector <Node> nodes;
for(;;){
nodes.push_back(u);
if(d[u.r][u.c][u.dir] == 0) break;
u = p[u.r][u.c][u.dir];
}
nodes.push_back(Node(r0, c0, dir));
int cnt = 0;
for(int i = (int)nodes.size() - 1; i >= 0; i--){
if(cnt % 10 == 0) printf(" ");
printf(" (%d,%d)", nodes[i].r, nodes[i].c);
if(++cnt % 10 == 0) printf("\n");
}
if(nodes.size() % 10 != 0) printf("\n");
}
void solve()
{
queue <Node> q;
memset(d, -1, sizeof(d));
Node u(r1, c1, dir);
d[r1][c1][dir] = 0;
q.push(u);
while(!q.empty())
{
Node u = q.front(); q.pop();
if(u.r == r2 && u.c == c2){
print_ans(u);
return ;
}
for(int i = 0; i < 3; i++){
Node v = walk(u, i);
if(has_edge[u.r][u.c][u.dir][i] && check(v.r, v.c) && d[v.r][v.c][v.dir] < 0){
d[v.r][v.c][v.dir] = d[u.r][u.c][u.dir] + 1;
p[v.r][v.c][v.dir] = u;
q.push(v);
}
}
}
printf(" No Solution Possible\n");//注意前面有2个空格。。
}
//bool input()
//{
// char s[99], s2[99];
// if(scanf("%s%d%d%s%d%d", s, &r0, &c0, s2, &r2, &c2) != 6) return false;
// printf("%s\n", s);
// dir = dir_id(s2[0]);
// r1 = r0 + dr[dir];
// c1 = c0 + dc[dir];
// memset(has_edge, 0, sizeof(has_edge));
// for(;;){
// int r, c;
// scanf("%d", &r);
// if(r == 0) break;
// scanf("%d", &c);
// while(scanf("%s", s) == 1 && s[0] != '*'){
// for(int i = 1; i < (int)strlen(s); i++){
// has_edge[r][c][dir_id(s[0])][turn_id(s[i])] = 1;
// }
// }
// }
// return true;
//}
bool read_case() {
char s[99], s2[99];
if(scanf("%s%d%d%s%d%d", s, &r0, &c0, s2, &r2, &c2) != 6) return false;
printf("%s\n", s);
dir = dir_id(s2[0]);
r1 = r0 + dr[dir];
c1 = c0 + dc[dir];
memset(has_edge, 0, sizeof(has_edge));
for(;;) {
int r, c;
scanf("%d", &r);
if(r == 0) break;
scanf("%d", &c);
while(scanf("%s", s) == 1 && s[0] != '*') {
for(int i = 1; i < strlen(s); i++)
has_edge[r][c][dir_id(s[0])][turn_id(s[i])] = 1;
}
}
return true;
}
int main()
{
while(read_case())
{
solve();
}
return 0;
}
【6-15】把每个变量看成一个点,“小于”关系看成有向边,则得到了一个有向图。这样,它们的实际任务就是把一个图所有的节点来排序,使得每一条有向边(u, v)对应的u都排在v的前面。所以这个题就是裸地拓扑排序了。这个题保证有解,不用判环。
int n, m;
int maze[200][200], in[200], ans[200];
int main()
{
while(scanf("%d%d", &n, &m) != EOF)
{
if(n == 0 && m == 0) break;
memset(maze, 0, sizeof(maze));
memset(in, 0, sizeof(in));
memset(ans, 0, sizeof(ans));
int x, y;
for(int i = 1; i <= m; i++){
scanf("%d%d", &x, &y);
maze[x][y] = 1;
in[y]++;
}
int k = 0;
queue <int> que;
for(int i = 1; i <= n; i++){
if(!in[i]) que.push(i);
}
while(que.size())
{
int u = que.front();
que.pop();
ans[++k] = u;
for(int i = 1; i <= n; i++){
if(maze[u][i]){
if(--in[i] == 0){
que.push(i);
}
}
}
}
for(int i = 1; i <= k; i++){
printf("%d%c", ans[i], i == k ? '\n' : ' ');
}
}
return 0;
}
【6-16】把字母看成节点,单词看成有向边,则问题有解,当且仅当图中有欧拉回路,有向图存在欧拉回路的条件有两个:1:图连通,这个用dfs来判断。而是度的关系,要么所有边的入度等于出度,要么只有2个点入度不等于出度并且一个点的入度等于出度+1,一个点的出度等于入度+1。
const int maxn = 100;
int G[100][100], in[100], out[100], vis[100];
int idx(char x){
return x - 'a';
}
void dfs(int u){
vis[u] = 1;
for(int i = 0; i < maxn; i++){
if(G[u][i] && !vis[i]){
dfs(i);
}
}
}
int main()
{
int T;
scanf("%d", &T);
while(T--){
memset(G, 0, sizeof(G));
memset(in, 0, sizeof(in));
memset(out, 0, sizeof(out));
memset(vis, 0, sizeof(vis));
int n;
scanf("%d", &n);
for(int i = 0; i < n; i++){
string line;
cin>>line;
int sz = line.size();
int x = idx(line[0]), y = idx(line[sz - 1]);
++out[x];
++in[y];
++G[x][y];
}
bool flag = 1;
int num1 = 0, num2 = 0;
for(int i = 0; i < maxn; i++) {
if(!flag) break;
if(in[i] != out[i]){
if(in[i] == out[i] + 1) num1++;
else if(in[i] + 1 == out[i]) num2++;
else{
flag = false;
break;
}
}
}
if(num1 && num2 && num1 + num2 > 2) flag = false;
if(flag)
{
for(int i = 0; i < maxn; i++){
if(out[i]){
dfs(i);
break;
}
}
bool ok = 1;
for(int i = 0; i < maxn; i++){
if(in[i] && !vis[i]){
ok = 0;
break;
}
if(out[i] && !vis[i]){
ok = 0;
break;
}
}
if(ok){
printf("Ordering is possible.\n");
}
else{
printf("The door cannot be opened.\n");
}
}
else{
printf("The door cannot be opened.\n");
}
}
return 0;
}
【6-17】直接在二维数组里面建树具体讲解看教材170页吧。
【6-18】 贴一个别人的解题链接吧。点击打开链接 看了很久才明白这种方法。
int g[maxn][maxn][maxn];
int x[maxn], y[maxn], z[maxn];
const int dx[] = {1, -1, 0, 0, 0, 0};
const int dy[] = {0, 0, 1, -1, 0, 0};
const int dz[] = {0, 0, 0, 0, 1, -1};
map <int, int> X, Y, Z;
int n;
LL space, vol;
int xnum, ynum, znum, mx, my, mz;
struct node{
int id;
int x0, y0, z0, x, y, z;
node(){}
node(int id, int x0, int y0, int z0, int x, int y, int z):id(id), x0(x0), y0(y0), z0(z0), x(x), y(y), z(z){}
bool operator<(const node &rhs) const{
return x0 < rhs.x0 || (x0 == rhs.x0 && y0 < rhs.y0) || (x0 == rhs.x0 && y0 == rhs.y0 && z0 < rhs.z0);
}
}r[maxn];
struct coord{
int x, y, z;
coord(){}
coord(int x, int y, int z) : x(x), y(y), z(z) {}
};
void draw(int pos, int id){
int x1 = X[r[pos].x0], x2 = X[r[pos].x0 + r[pos].x];
int y1 = Y[r[pos].y0], y2 = Y[r[pos].y0 + r[pos].y];
int z1 = Z[r[pos].z0], z2 = Z[r[pos].z0 + r[pos].z];
for(int i = x1; i < x2; i++){
for(int j = y1; j < y2; j++){
for(int k = z1; k < z2; k++){
g[i][j][k] = id;
}
}
}
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
memset(g, 0, sizeof(g));
memset(x, 0, sizeof(x));
memset(y, 0, sizeof(y));
memset(z, 0, sizeof(z));
X.clear();
Y.clear();
Z.clear();
for(int i = 0; i < n; i++){
scanf("%d%d%d%d%d%d", &r[i].x0, &r[i].y0, &r[i].z0, &r[i].x, &r[i].y ,&r[i].z);
r[i].id = i + 1;
x[i*2+1] = r[i].x0, x[i*2+2] = r[i].x0 + r[i].x;
y[i*2+1] = r[i].y0, y[i*2+2] = r[i].y0 + r[i].y;
z[i*2+1] = r[i].z0, z[i*2+2] = r[i].z0 + r[i].z;
}
sort(r, r + n);
sort(x + 1, x + 2 * n + 1);
sort(y + 1, y + 2*n + 1);
sort(z + 1, z + 2*n + 1);
xnum = unique(x + 1, x + 2 * n + 1) - (x + 1);
ynum = unique(y + 1, y + 2 * n + 1) - (y + 1);
znum = unique(z + 1, z + 2 * n + 1) - (z + 1);
x[0] = y[0] = z[0] = -1;
vol = space = 0;
for(int i = 1; i <= xnum; i++) X[x[i]] = i;
for(int i = 1; i <= ynum; i++) Y[y[i]] = i;
for(int i = 1; i <= znum; i++) Z[z[i]] = i;
for(int i = 0; i < n; i++){
draw(i, r[i].id);
}
vector <coord> v;
v.push_back(coord(0, 0, 0));
while(!v.empty())
{
coord cd = v.back(); v.pop_back();
int x = cd.x, y = cd.y, z = cd.z;
for(int i = 0; i < 6; i++){
int tx = x + dx[i];
int ty = y + dy[i];
int tz = z + dz[i];
if(tx >= 0 && tx <= xnum && ty >= 0 && ty <= ynum && tz >= 0 && tz <= znum && !g[tx][ty][tz]){
g[tx][ty][tz] = -1;
v.push_back(coord(tx, ty, tz));
}
}
}
for(int i = 1; i < xnum; i++){
for(int j = 1; j < ynum; j++){
for(int k = 1; k < znum; k++){
int l1 = x[i + 1] - x[i], l2 = y[j + 1] - y[j], l3 = z[k + 1] - z[k];
if(g[i][j][k] != -1) vol += l1*l2*l3;
for(int d = 0; d < 6; d++){
if(g[i][j][k] != -1 && g[i+dx[d]][j+dy[d]][k+dz[d]] == -1){
space += dx[d] ? l2 * l3 : dy[d] ? l1 * l3 : l1 * l2;
}
}
}
}
}
printf("%lld %lld\n", space, vol);
}
return 0;
}
【6-19】本题利用拓扑排序解决。拓扑排序适用于有向图,图中的结点满足给定的“连接”法则而形成一张有向图,通过拓扑排序,可以判断该图中是否含有有向环。本题如果直接按照题意去一个个地尝试拼接正方形,会很耗费时间,因为n的数目会非常大。如果我们进一步抽象,将正方形的标号看做一个拼接点,由于00不能作为拼接点,因此总共有26*2=52个点,那么如果存在另一个正方形B可以和正方形A相连接,意味着B中的那个拼接点可以到达A中所有的拼接点,这样就确定了连接法则,最终可以拼成一个有向图。
那么如何判断正方形能否拼成一个无限大的结构呢?通过观察可以发现,如果可以的话,那么这个结构中的一些正方形一定会重复出现,即他们之间的拼接点会重复出现!在有向图中即存在有向环!因此,只需要判断最终形成的有向图中是否含有有向环即可,利用拓扑排序即可完成判断。
int G[maxn][maxn];
int idx(char a1, char a2)
{
return (a1 - 'A') * 2 + (a2 == '+' ? 0 : 1);
}
void addedge(char a1, char a2, char b1, char b2)
{
if(a1 == '0' || b1 == '0') return;
int u = idx(a1, a2) ^ 1, v = idx(b1, b2);
G[u][v] = 1;
}
int c[maxn];
bool dfs(int u){
c[u] = -1;
for(int v = 0; v < maxn; v++){
if(G[u][v]){
if(c[v] < 0) return true;
else if(!c[v] && dfs(v)) return true;
}
}
c[u] = 1;
return false;
}
bool topsort(){
memset(c, 0, sizeof(c));
for(int u = 0; u < maxn; u++){
if(!c[u]){
if(dfs(u)){
return true;
}
}
}
return false;
}
int main()
{
int n;
while(scanf("%d", &n) != EOF && n)
{
memset(G, 0, sizeof(G));
while(n--){
char s[10];
scanf("%s", s);
for(int i = 0; i < 4; i++){
for(int j = 0; j < 4; j++){
if(i != j){
addedge(s[i*2], s[i*2+1], s[j*2], s[j*2+1]);
}
}
}
}
if(topsort())
{
printf("unbounded\n");
}
else{
printf("bounded\n");
}
}
return 0;
}
【6-20】本题的难点在于考虑字典序。考虑这样一种新的方法,我们从终点倒着BFS。得到每一个节点i到终点的最短距离d[i],然后从起点开始走,但是没到达一个新的节点要保证d的值恰好减少1(如果有多个选择可以随便走),直到到达终点。可以证明,这样走过的路径一定是一条最短路。有了这个结论,这个题就不难做了。直接从起点开始按照上诉规则走,如果有多种走法,选颜色字典序小的走,如果仍然有多种,就记录一下这些边的终点,下次走的时候要考虑所有这些终点,容易想到,这个算法的复杂度为O(m)。实际上一看到200000的数据, 我们设计的算法必须要非常高效,真的学习了。
struct edge{
int u, v, w;
edge(){}
edge(int u, int v, int w) : u(u), v(v), w(w){}
};
vector <edge> edges;
vector <int> G[maxn];
vector <int> ans;
int n, d[maxn], vis[maxn];
void addedge(int u, int v, int c){
edges.push_back(edge(u, v, c));
int sz = (int)edges.size() - 1;
G[u].push_back(sz);
}
void dfs2()
{
memset(vis, 0, sizeof(vis));
d[n - 1] = 0;
vis[n - 1] = 1;
queue <int> que;
que.push(n - 1);
while(!que.empty())
{
int v = que.front();
que.pop();
for(int i = 0; i < G[v].size(); i++){
int e = G[v][i];
int u = edges[e].v;
if(!vis[u]){
vis[u] = 1;
d[u] = d[v] + 1;
que.push(u);
}
}
}
}
void dfs1()
{
memset(vis, 0, sizeof(vis));
vis[0] = true;
ans.clear();
vector <int> nxt;
nxt.push_back(0);
for(int i = 0; i < d[0]; i++){
int mincol = INF;
for(int j = 0; j < nxt.size(); j++){
int u = nxt[j];
for(int k = 0; k < G[u].size(); k++){
int e = G[u][k];
int v = edges[e].v;
if(d[u] == d[v] + 1){
mincol = min(mincol, edges[e].w);
}
}
}
ans.push_back(mincol);
vector <int> nxt2;
for(int j = 0; j < nxt.size(); j++){
int u = nxt[j];
for(int k = 0; k < G[u].size(); k++){
int e = G[u][k];
int v = edges[e].v;
if(d[u] == d[v] + 1 && !vis[v] && edges[e].w == mincol){
nxt2.push_back(v);
vis[v] = 1;
}
}
}
nxt = nxt2;
}
}
void print_ans()
{
printf("%d\n", ans.size());
printf("%d", ans[0]);
for(int i = 1; i < ans.size(); i++){
printf(" %d", ans[i]);
}
printf("\n");
}
int main()
{
int m, x, y, z;
while(scanf("%d%d", &n, &m) != EOF)
{
edges.clear();
for(int i = 0; i < n; i++) G[i].clear();
while(m--)
{
scanf("%d%d%d", &x, &y, &z);
addedge(x - 1, y - 1, z);
addedge(y - 1, x - 1, z);
}
dfs2();
dfs1();
print_ans();
}
return 0;
}
【6-21】这个题实际上就是个模拟题,反正有点恶心,贴一位大牛的解题报告吧。 点击打开链接
【6-22】脑洞题,非常有趣。这个在分类里面叫对偶图。本题初看起来比较麻烦,不妨简化一下:先判断是否有解,再考虑如何求出解。根据题意描述,相当于在一个正方形中有若干个圆形障碍物,问是否能从左边界走到右边界。判断是否有解需要一点创造性的思维:不妨把正方形当做一个湖,所有的圆形都是垫脚石,假设我们可以从上边界“踩着”垫脚石成功走到下边界,说明左右边界是不连通的;否则就是连通的。想到了这里,便不难用dfs或bfs来判断是否有解了:每次都从和上边界相交的圆开始进行dfs,如果遇到某个圆和下边界也相交,那么无解。那么如何寻找解呢,根据题意需要找到最靠近北边的。这样的话,只需要找到所有与左边界相交的圆中最靠南边的交点即可,同理可以找到右边界的解。本题在数据处理上有一个小技巧:把圆形设计成一个结构体,维护圆心坐标和半径,同时用read成员函数读入数据,比用scanf函数方便了很多。
struct Circle{
int x, y, r;
void read(){
scanf("%d%d%d", &x, &y, &r);
}
}c[maxn];
double ans1, ans2;
int n, vis[maxn];
bool jiao(Circle a, Circle b){
int dx = a.x - b.x;
int dy = a.y - b.y;
return dx * dx + dy * dy - (a.r + b.r) * (a.r + b.r) <= 0;
}
bool dfs(int u){
vis[u] = 1;
if(c[u].y - c[u].r <= 0) return false;
//和左边界有交点
if(c[u].x - c[u].r <= 0) ans1 = min(ans1, c[u].y - sqrt(c[u].r * c[u].r - c[u].x * c[u].x));
//和右边界有交点
if(c[u].x + c[u].r >= 1000) ans2 = min(ans2, c[u].y - sqrt(c[u].r * c[u].r - (1000 - c[u].x) * (1000 - c[u].x)));
for(int i = 0; i < n; i++){
if(!vis[i]){
if(jiao(c[u], c[i])){
if(!dfs(i)){
return false;
}
}
}
}
return true;
}
void work(){
ans1 = ans2 = 1000.00;
for(int i = 0; i < n; i++){
if(!vis[i] && c[i].y + c[i].r >= 1000){
if(!dfs(i)){
printf("IMPOSSIBLE\n");
return ;
}
}
}
printf("0.00 %.2f 1000.00 %.2f\n", ans1, ans2);
}
int main()
{
while(scanf("%d", &n) != EOF)
{
memset(vis, 0, sizeof(vis));
for(int i = 0; i < n; i++){
c[i].read();
}
work();
}
return 0;
}
【后记】 到这里,第6章的例题就写完了,接下来就是独立完成习题了,通过这些例题的学习掌握了很多耳目一新的知识,同时也提高了对基础数据结构的认识,对之后的学习会有很大帮助,加油。