首页 > 代码库 > 紫书第4章 函数和递归
紫书第4章 函数和递归
1 序
系统的整理下第四章的学习笔记。同上次一样,尽量在不依赖书本的情况下自己先把例题做出来。这次有许多道题代码量都比较大,在例题中我都用纯C语言编写,但由于习题的挑战性和复杂度,我最终还是决定在第五章开始前,就用C++来完成习题。不过所有的代码都是能在C++提交下AC的。
在习题中,我都习惯性的构造一个类来求解问题,从我个人角度讲,这会让我的思路清晰不少,希望自己的一些代码风格不会影响读者对解题思路的理解。
其实在第四章前,我就顾虑着是不是真的打算把题目全做了,这些题目代码量这么大,要耗费很长一段时间。事实证明我多虑了,一分付出一分收获,经过两周的练习,我的编码能力进步很大。以前比赛一直很怕复杂麻烦的模拟题,现在遇到模拟题反而很开心,因为我算法题会的很少啊~~~
我是从前往后花了两周把题目做一遍的,然后又从后往前整理出每道题的题解,再整理出博客。一些东西弄着弄着可能会没耐心了或心烦,忽悠了事,所以写的不好、有错的地方读者尽管指出,我会修改完善。
建议配合紫书——《算法竞赛入门经典(第2版)》阅读本博客,转载请注明出处:code4101,谢谢。
2 例题
2.1 UVa1339--Ancient Cipher
思路同书本。只要将两个字符串s1、s2的26个字母分别出现的次数统计出来,存在int a[26]与b[26],然后a、b数组按数值从小到大排序后,看a、b是否相等就能知道能否一一映射。
#include <stdio.h> #include <stdlib.h> #include <string.h> int cmp(const void* a,const void* b) { return *(int *)a - *(int *)b; } int main() { char s1[110], s2[110]; while (scanf("%s%s", s1, s2) != EOF) { int a[26] = {}, b[26] = {}, i; for (i = 0; s1[i]; i++) a[s1[i]-'A']++; for (i = 0; s2[i]; i++) b[s2[i]-'A']++; qsort(a, 26, sizeof(int), cmp); qsort(b, 26, sizeof(int), cmp); printf("%s\n", memcmp(a, b, sizeof(a))?"NO":"YES"); } return 0; }
2.2 UVa489--Hangman Judge
这题小白书做过,我是用标记数组,来判断哪些字母是否有访问过来求解的。
#include <stdio.h> int main() { int t, i, c; char s[1000]; bool vis[26], has[26]; while (scanf("%d ", &t), t != -1) { for (i = 0; i < 26; i++) vis[i] = has[i] = 0; scanf("%s", s); for (i = 0; s[i]; i++) has[s[i]-'a'] = 1; for (i = 0; i < 26; i++) vis[i] = has[i]; scanf("%s", s); for (c = i = 0; s[i]; i++) { if (has[s[i]-'a']) vis[s[i]-'a'] = 0; else c++; if (c == 7) break; } for (i = 0; i < 26; i++) if (vis[i]) break; printf("Round %d\n", t); if (i == 26 && c <= 7) printf("You win.\n"); else if (i < 26 && c < 7) printf("You chickened out.\n"); else printf("You lose.\n"); } return 0; }
2.3 UVa133--The Dole Queue
一开始的思路不太好,我想用一个数组,每有1个或2个离队,就把其值对应删掉(用数组平移,n递减来实现),导致条件分支太多。
其实这题数据非常小,离队的用0值来标记,就不会变的这么麻烦了。
代码是模仿书上的。
其实这题数据非常小,离队的用0值来标记,就不会变的这么麻烦了。
代码是模仿书上的。
#include <stdio.h> int n, k, m, a[25]; int go(int p, int d, int t) { while (t--) { do { p = (p+d+n-1) % n + 1; } while (a[p] == 0); } return p; } int main() { while (scanf("%d%d%d", &n, &k, &m), n&&k&&m) { for (int i = 1; i <= n; i++) a[i] = i; int left = n; int p1 = n, p2 =1; while (left) { p1 = go(p1, 1, k); p2 = go(p2, -1, m); printf("%3d", p1); left--; if (p2 != p1) { printf("%3d", p2); left--;} a[p1] = a[p2] = 0; if (left) printf(","); } printf("\n"); } return 0; }
2.4 UVa213--Message Decoding
我先从LRJ讲的思路和框架入手,然后在根据自己的习惯优化代码。
题目说了编码头(header)只占一行,所以没必要像LRJ的readcodes那么复杂,我直接用my_gets读入header字符串s,然后用init函数与s来初始化code。
题目说了编码头(header)只占一行,所以没必要像LRJ的readcodes那么复杂,我直接用my_gets读入header字符串s,然后用init函数与s来初始化code。
#include <stdio.h> #include <string.h> char code[8][1<<8], s[1<<8]; // 过滤空行,返回字符串长度 int my_gets(char* s) { int k; while ((k = getchar()) == '\n'); // 空行重读 if (k == -1) k = 0; else gets(s+1); s[0] = k; return strlen(s); } void init() { int i, j, k = 0; for (i = 1; ; i++) { for (j = 0; j < (1<<i)-1; j++) { code[i][j] = s[k++]; if (!s[k]) return; } } } // 读取01字符,返回0、1整型值 int read() { char ch; while ((ch = getchar()) == '\n'); return ch - '0'; } // 读取c个01字符 int readint(int c) { int v = 0; while (c--) v = (v<<1)+read(); return v; } int main() { int len, v; while (my_gets(s)) { init(); while (len = readint(3)) while ((v = readint(len)) != ((1<<len)-1)) putchar(code[len][v]); putchar('\n'); } return 0; }
2.5 UVa512--Spreadsheet Tracking
定义D[51][51][2],D[i][j][0]代表初始位置在第i行第j的元素当前在的行数,D[i][j][1]代表所在列。
其他操作都好写,就是EX的交换操作挂了。WA 5次全是错在交换这里。其实这题自己想出的解题方法还好,主要不是思路不合适的原因。
其他操作都好写,就是EX的交换操作挂了。WA 5次全是错在交换这里。其实这题自己想出的解题方法还好,主要不是思路不合适的原因。
主要问题就是自己没有处理好查找问题,没想过:可不可能找不到?边查边修改值也是兵家之大忌,迫不得已这样操作时要万万小心。
#include <stdio.h> #include <stdlib.h> #include <string.h> #define FOR() for (int i = 1; i <= R; i++) for (int j = 1; j <= C; j++) int R, C, D[51][51][2]; int cmp(const void* a, const void* b) {return *(int *)a-*(int *)b;} void init() { FOR() D[i][j][0] = i, D[i][j][1] = j; } void EX() { int r1, c1, r2, c2; scanf("%d%d%d%d", &r1, &c1, &r2, &c2); FOR() if (D[i][j][0]==r1&&D[i][j][1]==c1) {D[i][j][0] = r2; D[i][j][1] = c2;} else if (D[i][j][0]==r2&&D[i][j][1]==c2) {D[i][j][0] = r1; D[i][j][1] = c1;} } void DR(int r) { FOR() { if (D[i][j][0] == r) D[i][j][0] = 0; else if (D[i][j][0] > r) D[i][j][0]--; } } void DC(int c) { FOR() { if (D[i][j][1] == c) D[i][j][0] = 0; else if (D[i][j][1] > c) D[i][j][1]--; } } void IR(int r) { FOR() if (D[i][j][0] >= r) D[i][j][0]++; } void IC(int c) { FOR() if (D[i][j][1] >= c) D[i][j][1]++; } int main() { int kase, cnt, i, j, r, c, a[15], k; char cmd[5]; for (kase = 1; scanf("%d%d",&R,&C), R; kase++) { init(); scanf("%d", &cnt); for (i = 0; i < cnt; i++) { scanf("%s", cmd); if (!strcmp(cmd,"EX")) EX(); else { scanf("%d", &k); for (j = 0; j < k; j++) scanf("%d", a+j); qsort(a, k, sizeof(int), cmp); int dt = 1; void (*pf)(int); // 函数指针 if (!strcmp(cmd,"DR")) pf = DR, dt = -1; else if (!strcmp(cmd,"DC")) pf = DC, dt = -1; else if (!strcmp(cmd,"IR")) pf = IR; else pf = IC; for (j = 0; j < k; j++) pf(a[j]+dt*j); } } scanf("%d", &cnt); if (kase != 1) putchar('\n'); printf("Spreadsheet #%d\n", kase); for (i = 0; i < cnt; i++) { scanf("%d%d", &r, &c); printf("Cell data in (%d,%d) ", r, c); if (D[r][c][0]) printf("moved to (%d,%d)\n", D[r][c][0], D[r][c][1]); else puts("GONE"); } } return 0; }
2.6 UVa12412--A Typical Homework (a.k.a Shi Xiong Bang Bang Mang)
Show_ranking()
排名计算可以把总分存在另一个数组s,按从大到小排序,然后用二分查找找lower_bound。
当然,第四章还没讲到二分法,而且这里数据很小,也没必要二分,直接遍历搜索就行了。
Show_Statistics():需要计算每人通过多少科目,每门课有多少人通过。
可以用一个二维表来存储通过信息。中间用0、1变量表示是否合格,每行、每列均求和。利用这个二维表的信息就能完成最后一个函数的编写了(实际只要存储“行和”和“列和”就行了)。
还有就是精度问题,浮点数输出都要加eps,出题人显然故意设了特殊数据,不加eps绝对WA。
最后output这么长,调试起来不是肉眼能解决的,所以推荐用个文件比较程序:文件比较函数diff。
排名计算可以把总分存在另一个数组s,按从大到小排序,然后用二分查找找lower_bound。
当然,第四章还没讲到二分法,而且这里数据很小,也没必要二分,直接遍历搜索就行了。
Show_Statistics():需要计算每人通过多少科目,每门课有多少人通过。
可以用一个二维表来存储通过信息。中间用0、1变量表示是否合格,每行、每列均求和。利用这个二维表的信息就能完成最后一个函数的编写了(实际只要存储“行和”和“列和”就行了)。
还有就是精度问题,浮点数输出都要加eps,出题人显然故意设了特殊数据,不加eps绝对WA。
最后output这么长,调试起来不是肉眼能解决的,所以推荐用个文件比较程序:文件比较函数diff。
#include <stdio.h> #include <stdlib.h> #include <string.h> #define maxn 105 const double eps = 1e-5; // 比保留2位有效数字多3个数量级 struct Student{ char SID[50], name[50]; int CID, sub[4], sco;// sub(subject),下标0123分别代表语数英编程成绩 }stu[maxn], *p; int sum = 0, sco[maxn]; void menu() { char s[6][50] = {"Add", "Remove", "Query", "Show ranking", "Show Statistics", "Exit"}; printf("Welcome to Student Performance Management System (SPMS).\n\n"); for (int i = 1; i <= 6; i++) printf("%d - %s\n", i%6, s[i-1]); putchar('\n'); } Student* find_SID(char* SID) { for (int i = 0; i < sum; i++) if (!strcmp(SID, stu[i].SID)) return stu+i; return NULL; } void Add() { while (puts("Please enter the SID, CID, name and four scores. Enter 0 to finish."), p = stu+sum, scanf("%s", p->SID), strcmp(p->SID, "0")) { scanf("%d%s", &p->CID, p->name); for (int i = 0; i < 4; i++) scanf("%d", &p->sub[i]); if (find_SID(p->SID) != NULL) puts("Duplicated SID."); else sum++; } } void Remove() { char s[50]; while (puts("Please enter SID or name. Enter 0 to finish."), scanf("%s", s), strcmp(s, "0")) { int xx = 0; for (int i = 0; i < sum; i++) if (!strcmp(s,stu[i].SID) || !strcmp(s,stu[i].name)) { sum--; xx++; for (int j = i; j < sum; j++) stu[j] = stu[j+1]; i--; } printf("%d student(s) removed.\n", xx); } } int cmp(const void* a, const void* b) { return *(int*)b-*(int*)a;} int ranklist(int t) { int i = -1; while (sco[++i]>t); return i+1; } void Query() { int i, j; char s[50]; for (i = 0; i < sum; i++) { stu[i].sco = stu[i].sub[0]; for (j = 1; j < 4; j++) stu[i].sco += stu[i].sub[j]; sco[i] = stu[i].sco; } qsort(sco, sum, sizeof(int), cmp); while (puts("Please enter SID or name. Enter 0 to finish."), scanf("%s", s), strcmp(s, "0")) { for (i = 0; i < sum; i++) if (!strcmp(s,stu[i].SID) || !strcmp(s,stu[i].name)) { printf("%d %s %d %s", ranklist(stu[i].sco), stu[i].SID, stu[i].CID, stu[i].name); for (j = 0; j < 4; j++) printf(" %d", stu[i].sub[j]); printf(" %d %.2lf\n", stu[i].sco, stu[i].sco/4.0 + eps); } } } void Show_ranking() { puts("Showing the ranklist hurts students' self-esteem. Don't do that."); } void Show_Statistics() { puts("Please enter class ID, 0 for the whole statistics."); // c[i]:第i门课有多少人合格; cnt[i]:过i门课恰好有多少人 int r, c[4] = {}, cnt[5] = {}; int i, j, n = 0, s[4] = {}, cid; // n人数,s[i]:第i门课的总分,cid班级编号 scanf("%d", &cid); for (i = 0; i < sum; i++) if (!cid || stu[i].CID == cid) { n++; for (r = j = 0; j < 4; j++) { s[j] += stu[i].sub[j]; if (stu[i].sub[j] >= 60) { r++; c[j]++; } } cnt[r]++; } char str[4][50] = {"Chinese", "Mathematics", "English", "Programming"}; for (i = 0; i < 4; i++) { printf("%s\nAverage Score: %.2lf\n", str[i], 1.0*s[i]/n + eps); printf("Number of passed students: %d\nNumber of failed students: %d\n\n", c[i], n-c[i]); } printf("Overall:\nNumber of students who passed all subjects: %d\n", cnt[4]); for (i = 3; i > 0; i--) printf("Number of students who passed %d or more subjects: %d\n", i, cnt[i]+=cnt[i+1]); printf("Number of students who failed all subjects: %d\n\n", n - cnt[1]); } int main() { #ifdef DEBUG freopen("12412.in", "r", stdin); freopen("12412.ans", "w", stdout); #endif int c; while (menu(), scanf("%d", &c), c) { if (c == 1) Add(); else if (c == 2) Remove(); else if (c == 3) Query(); else if (c == 4) Show_ranking(); else if (c == 5) Show_Statistics(); } return 0; }最后看LRJ的代码,竟然没有用结构体解!
3 习题
3.1 UVa1589--Xiangqi
习题第一道就极具挑战性,为了思路清晰,我还是决定用C++,构造一个Xiangqi类来求解。
然后要好好分析好思路再动手敲代码。首先黑子会动很麻烦,能不能把动态问题改成静态?如果题目只是给你一个局面,问你当前红方动,能不能把黑方吃掉是不是简单多了?所以要生成一个Xiangqi对象A,按input数据初始化。然后只要黑将不跟红帅面对面直接吃掉,他至多有四种移动方案,用A来生成黑将移动后的Xiangqi对象B。如上,B是一个静态问题。
那么现在关键是Xiangqi类的书写,棋盘存到二维数组board中不做修改。红帅和黑将是棋盘的核心,所以要定义ax,ay,bx,by存储它们所在的坐标,坐标也从1开始编号,不容易混乱思路,而且留有边界board[0],一些操作也会很方便。
车、将、炮吃子的本质是相同的。都是要在同一横线或竖线,但中间隔的棋子一种是0个,一种是1个。这种操作可以定义一个基础函数Cnt,可以计算(x,y)与黑将是否在同一横线或竖线,不满足则返回-1。否则返回两坐标间隔的棋子数。注意这里的技巧,不是计算任意两点,而是任意一点到黑将,因为所有的子都是围绕黑将展开的,都是要去吃它。这样就能简化函数节省代码量。
马的吃子看似麻烦,其实符合很特别的数量规律。记其行列偏移量分别为dx和dy,有没注意到dx*dy的绝对值一定是2?“蹩马腿”的技巧也是一样,详见函数H的代码。
剩下的代码应该不难吧?再解释汉字比代码都长了~~~
然后要好好分析好思路再动手敲代码。首先黑子会动很麻烦,能不能把动态问题改成静态?如果题目只是给你一个局面,问你当前红方动,能不能把黑方吃掉是不是简单多了?所以要生成一个Xiangqi对象A,按input数据初始化。然后只要黑将不跟红帅面对面直接吃掉,他至多有四种移动方案,用A来生成黑将移动后的Xiangqi对象B。如上,B是一个静态问题。
那么现在关键是Xiangqi类的书写,棋盘存到二维数组board中不做修改。红帅和黑将是棋盘的核心,所以要定义ax,ay,bx,by存储它们所在的坐标,坐标也从1开始编号,不容易混乱思路,而且留有边界board[0],一些操作也会很方便。
车、将、炮吃子的本质是相同的。都是要在同一横线或竖线,但中间隔的棋子一种是0个,一种是1个。这种操作可以定义一个基础函数Cnt,可以计算(x,y)与黑将是否在同一横线或竖线,不满足则返回-1。否则返回两坐标间隔的棋子数。注意这里的技巧,不是计算任意两点,而是任意一点到黑将,因为所有的子都是围绕黑将展开的,都是要去吃它。这样就能简化函数节省代码量。
马的吃子看似麻烦,其实符合很特别的数量规律。记其行列偏移量分别为dx和dy,有没注意到dx*dy的绝对值一定是2?“蹩马腿”的技巧也是一样,详见函数H的代码。
剩下的代码应该不难吧?再解释汉字比代码都长了~~~
#include <algorithm> #include <cmath> #include <cstdio> #include <cstring> #include <iostream> using namespace std; struct Xiangqi { int board[11][10]; int ax, ay, bx, by; // a是红方的帅,b是黑方的将; x,y是坐标 // 构造函数遵循题目的输入格式 Xiangqi(int n, int _bx, int _by): bx(_bx), by(_by) { memset(board, 0, sizeof(board)); board[bx][by] = 'Y'; // 用字母Y代表黑将 char type[5]; int x, y; for (int i = 0; i < n; i++) { scanf("%s%d%d", type, &x, &y); board[x][y] = type[0]; } } // 黑将做(dx,dy)的移动,移动成功则返回1,否则0 int go(int dx, int dy) { if (bx+dx>0 && bx+dx<4 && by+dy>3 && by+dy<7) { board[bx][by] = 0; bx += dx; by += dy; board[bx][by] = 'Y'; return 1; } return 0; } // 判断(x,y)到(bx,by)中间一共有多少棋子,重叠与不在同一竖直、水平线返回-1 int Cnt(int x, int y) { int cnt = -1, i, j; if (x == bx && y != by) for (cnt = 0, j = min(y,by)+1; j < max(y,by); j++) if (board[x][j]) cnt++; if (x != bx && y == by) for (cnt = 0, i = min(x,bx)+1; i < max(x,bx); i++) if (board[i][y]) cnt++; return cnt; } // 判断在(x,y)的马能不能吃掉黑将 int H(int x, int y) { int dx = bx - x, dy = by - y; if (abs(dx*dy) != 2) return 0; if (abs(dx)==2) if (board[x+dx/2][y]) return 0; if (abs(dy)==2) if (board[x][y+dy/2]) return 0; return 1; } // 判断如果当前棋盘红方移动,黑方是否被将死 int isBlackLost() { for (int i = 1; i <= 10; i++) for (int j = 1; j <= 9; j++) { switch (board[i][j]) { case 'G': case 'R': if (Cnt(i,j)==0) return 1; break; case 'H': if (H(i,j)) return 1; break; case 'C': if (Cnt(i,j)==1) return 1; } } return 0; } void out() { // 用于输出棋盘进行调试的函数 printf("**123456789\n"); for (int i = 1; i <= 10; i++) { printf("%2d", i); for (int j = 1; j <= 9; j++) { putchar(board[i][j]?board[i][j]:' '); } putchar('\n'); } putchar('\n'); } }; int isCheckmate(Xiangqi& A) { int dx[4] = {-1,1,0,0}, dy[4] = {0,0,1,-1}; for (int i = 0; i < 4; i++) { Xiangqi B(A); // 用复制构造函数拷贝副本 if (B.go(dx[i],dy[i]) && !B.isBlackLost()) return 0; } return 1; } int main() { #ifdef DEBUG freopen("1589.in", "r", stdin); #endif // DEBUG int n, bx, by; while (scanf("%d%d%d", &n, &bx, &by), n) { Xiangqi A(n, bx, by); //A.out(); //cout << A.Cnt(6,4) << endl; //cout << A.isBlackLost() << endl; //A.out(); //printf("答案: %d\n", isCheckmate(A)); printf("%s\n", isCheckmate(A)?"YES":"NO"); } return 0; }
3.2 UVa201--Squares
这题紫书翻译有点小瑕疵,不过问题不大能理解题目啦...V给的第1个数表示列,第2个数表示行。
我构造一个Squares类,用h和v直接标记[i][j]是否存在向前或向下的横线竖线。本题关键是写一个isSqu函数,判断以两点为对角线是否能形成闭环,这个函数很有技巧,类似经典的蛇形填数题,读者慢慢领悟。(整理解题报告的过程中,我发现在类里写成员函数,先后顺序竟然没有影响!也就是该段代码构造函数写在isSqu等前面也可以编译通过)。
我构造一个Squares类,用h和v直接标记[i][j]是否存在向前或向下的横线竖线。本题关键是写一个isSqu函数,判断以两点为对角线是否能形成闭环,这个函数很有技巧,类似经典的蛇形填数题,读者慢慢领悟。(整理解题报告的过程中,我发现在类里写成员函数,先后顺序竟然没有影响!也就是该段代码构造函数写在isSqu等前面也可以编译通过)。
#include <cstdio> #include <cstring> #include <iostream> using namespace std; struct Squares { int h[10][10], v[10][10]; int n, cnt[10]; // 接下来判断(x1,y1)-(x2,y2)能否连环 int isSqu(int x1, int y1, int x2, int y2) { int px = x1, py = y1; while (py < y2) { if (!h[px][py]) return 0; py++; } while (px < x2) { if (!v[px][py]) return 0; px++; } while (py > y1) { if (!h[px][py-1]) return 0; py--; } while (px > x1) { if (!v[px-1][py]) return 0; px--; } return 1; } void Count() { int i, j, k; memset(cnt, 0, sizeof(cnt)); for (i = 1; i < n; i++) for (j = 1; j < n; j++) for (k = 1; i+k<=n && j+k<=n; k++) if (isSqu(i,j,i+k,j+k)) cnt[k]++; } Squares(int _n, int m):n(_n) { memset(h, 0, sizeof(h)); memset(v, 0, sizeof(v)); char str[10]; int x, y, i; for (i = 0; i < m; i++) { scanf("%s%d%d", str, &x, &y); if (!strcmp(str, "H")) h[x][y] = 1; else v[y][x] = 1; } Count(); } void Msg() { int logo = 1; for (int i = 1; i < n; i++) if (cnt[i]) { logo = 0; printf("%d square (s) of size %d\n", cnt[i], i); } if (logo) puts("No completed squares can be found."); } }; int main() { int n, m, kase = 0; while (cin >> n >> m) { Squares A(n, m); if (kase) puts("\n**********************************\n"); printf("Problem #%d\n\n", ++kase); A.Msg(); } return 0; }
3.3 UVa220--Othello
首先构造一个Othello类。有一个board二维棋盘,还有一个cur存储当前行动的是‘W‘还是‘B‘。首先肯定有对应的构造函数Othello;然后为了方便行动对象转换,写了一个NextCur函数;最后黑白棋子的统计由一个Count函数完成。接着写L命令的List函数,Mrc命令的MakeMove是题目核心。列好框架后,思路就清晰了,慢慢完成相应的函数模块即可。
为了完成L和Mrc命令,有个函数是必须的,就是在某一点,向外8个方向遍历操作。这里用walk函数来完成,并通过一个change参数标记操作是仅查找判断,还是修改棋盘状态。这里8方向的遍历是模拟题很常见的内容,我通常的实现方法是对8个方向按顺时针编号0~7:
有了walk函数后,就能写出,在(x,y)放置棋子是否合法的操作isMakeOk。那么List函数就是遍历棋盘,把合法且board[i][j]为空的坐标输出即可。MakeMove也只是调用改变棋盘状态的walk操作。
最后注意输出格式,第39行要用%3d输出,题目根本没描述,要自己看样例输出才知道。当然这个坑算好的了,等做到数据挖掘那题读者就知道什么才是真正的坑。
为了完成L和Mrc命令,有个函数是必须的,就是在某一点,向外8个方向遍历操作。这里用walk函数来完成,并通过一个change参数标记操作是仅查找判断,还是修改棋盘状态。这里8方向的遍历是模拟题很常见的内容,我通常的实现方法是对8个方向按顺时针编号0~7:
用两个数组dirx[i]和diry[i]分别表示i方向的行偏移量和列偏移量。
有了walk函数后,就能写出,在(x,y)放置棋子是否合法的操作isMakeOk。那么List函数就是遍历棋盘,把合法且board[i][j]为空的坐标输出即可。MakeMove也只是调用改变棋盘状态的walk操作。
最后注意输出格式,第39行要用%3d输出,题目根本没描述,要自己看样例输出才知道。当然这个坑算好的了,等做到数据挖掘那题读者就知道什么才是真正的坑。
#include <cstdio> #include <cstring> #include <iostream> using namespace std; struct Othello { char board[10][10], cur; Othello() { memset(board, 0, sizeof(board)); for (int i = 1; i < 9; i++) scanf("%s", &board[i][1]); char str[5]; scanf("%s", str); cur = str[0]; } char NextCur() { return cur=='B'?'W':'B'; } void OutBoard() { for (int i = 1; i < 9; i++) puts(&board[i][1]); } void List() { int cnt = 0, i, j; for (i = 1; i < 9; i++) for (j = 1; j < 9; j++) if (board[i][j] == '-' && isMakeOk(i,j)) { if (cnt) putchar(' '); cnt++; printf("(%d,%d)", i, j); } if (cnt) putchar('\n'); else puts("No legal move."); } void Count() { int b = 0, w = 0; for (int i = 1; i < 9; i++) for (int j = 1; j < 9; j++) if (board[i][j]=='B') b++; else if (board[i][j]=='W') w++; printf("Black -%3d White -%3d\n", b, w); } // 从(x,y)出发,沿dir所指方向查找. change确定要不要进行修改操作 //返回值表示该条路径是否可以进行删除 int walk(int x, int y, int dir, int change = 0) { static int dirx[8] = {-1,-1,0,1,1,1,0,-1}; static int diry[8] = {0,1,1,1,0,-1,-1,-1}; int dx = dirx[dir], dy = diry[dir], k = 0; char ch1 = NextCur(), ch; //ch1存与cur对应的另一种字符 do { k++; ch = board[x+k*dx][y+k*dy]; if (ch == '_' || !ch) return 0; } while (ch == ch1); if (k == 1 || ch != cur) return 0; if (change) for (int i = 1; i < k; i++) board[x+i*dx][y+i*dy] = cur; return 1; } // 当前棋子能否放在(x,y) int isMakeOk(int x, int y) { for (int i = 0; i < 8; i++) if (walk(x,y,i)) return 1; return 0; } void MakeMove(int x, int y) { if (!isMakeOk(x,y)) cur = NextCur(); board[x][y] = cur; for (int i = 0; i < 8; i++) walk(x,y,i,1); cur = NextCur(); Count(); } }; int main() { int T, kase; char c[10]; // choose cin >> T; for (kase = 1; kase <= T; kase++) { if (kase != 1) putchar('\n'); Othello A; while (scanf("%s", c), strcmp(c, "Q")) { if (c[0] == 'L') A.List(); else A.MakeMove(c[1]-'0', c[2]-'0'); } A.OutBoard(); } return 0; }
3.4 UVa253--Cube painting
这题一开始把自己转晕了,WA了几发,然后看了shiqi_614的博客,思路清晰多了。这题要怎么“暴力”呢?首先自己要定义一个规范。比如我的是把B的六个面依次朝上,然后有四种左右水平旋转操作,只要6*4=24中有一种B能跟A完全相同,那他们就是等价的。
Cube类中,先定义了基本的旋转操作函数tran,然后只定义了从2往1的向上翻和从2往4的向右翻操作,为了增强函数通用性,up、right都一个带一个旋转次数的参数,负数代表反向旋转,因为本质上只有四种变换,注意这两个函数代码的实现原理,a[i]=j(程序里编号从0开始),表示原来编号为i的面,翻转到第j面。
有了这两种操作,就不难写出剩下的比较操作了。为了方便,把四种旋转操作的判断,封装到函数Equal,两个Cube类是否等价通过重载==运算符来判断。
#include <iostream> #include <string> using namespace std; struct Cube { string s; Cube(string _s): s(_s) { } void tran(int* a, int k) { for (int i = 0; i < k; i++) { string t(6,0); for (int j = 0; j < 6; j++) t[a[j]] = s[j]; s = t; } } void up(int k = 1) { int a[6] = {4,0,2,3,5,1}; tran(a, ((k%4)+4)%4); } void right(int k = 1) { int a[6] = {0,3,1,4,2,5}; tran(a, ((k%4)+4)%4); } bool Equal(Cube A, Cube B) { for (int i = 0; i < 4; i++) { B.right(); if (A.s == B.s) return 1; } return 0; } bool operator == (Cube B) { string s0 = B.s; int r[6] = {0,0,1,1,0,0}, u[6] = {0,1,1,-1,-1,2}; for (int i = 0; i < 6; i++) { B.s = s0; B.right(r[i]); B.up(u[i]); if (Equal(*this, B)) return 1; } return 0; } }; int main() { string s; while (cin >> s) { Cube A(s.substr(0,6)), B(s.substr(6,6)); cout << (A==B?"TRUE":"FALSE") << "\n"; } return 0; }
3.5 UVa1590--IP Networks
题意其实就是计算二进制的最大公共前缀。
这里刚好能用无符号32位整型来存储IP,然后就是一些位运算的技巧。子网掩码mask初始化为0xffffffff。然后一直左移遍历,直到所有IP和当前mask对应的IP地址都相等时,退出循环。
这里刚好能用无符号32位整型来存储IP,然后就是一些位运算的技巧。子网掩码mask初始化为0xffffffff。然后一直左移遍历,直到所有IP和当前mask对应的IP地址都相等时,退出循环。
#include <cstdio> #include <iostream> using namespace std; struct Network { unsigned int ip[1005], addr, mask; int n; void init() { int logo; mask = 0xffffffff; do { logo = 0; addr = ip[0]&mask; for (int i = 1; i < n; i++) if ((ip[i]&mask) != addr) logo = 1; if (logo) mask <<= 1; } while (logo); } Network(int _n): n(_n) { unsigned int a[4]; for (int i = 0; i < n; i++) { scanf("%u.%u.%u.%u", &a[0], &a[1], &a[2], &a[3]); ip[i] = (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3]; } init(); } }; void OutIp(unsigned a) { for (int i = 0; i < 3; i++) printf("%d.", (a>>(3-i)*8)&0xff); printf("%d\n", a&0xff); } int main() { int n; while (cin >> n) { Network A(n); OutIp(A.addr); OutIp(A.mask); } return 0; }
3.6 UVa508--Morse Mismatches
首先,这道题目紫书翻译错了。精确匹配的话应该输出第一个,而不是任意一个,如果有多个精确匹配,要加后缀“!”。未精确匹配的,不管是否有多个,都应该要加“?”。有关紫书勘误的信息,见官方链接(要是卡了就多F5一下)。
回到该题,这题的正确率确实很吓人,把我也雷住了,第一发WA后就信心大失。然后在想是不是题意理解错了,试了各种各样的姿势,还是WA。最后找到AC该题12位中的CKSteven(他还未写该题博客),看了他的代码后才发现自己最开始的题意理解没错,是有一个bug(见代码第14行),不然能1A的。
这道题题意原文说的很清楚。我这里主要是定义了一个dist函数,来判断两个字符串的“距离”。相同距离就是0。然后如果短的字符串(设为a)不是长的字符串(设为b)的子串,距离就是inf,否则距离就是b的长度减a的长度,这个操作就是增加或删除字符尽量少的等价含义。
回到该题,这题的正确率确实很吓人,把我也雷住了,第一发WA后就信心大失。然后在想是不是题意理解错了,试了各种各样的姿势,还是WA。最后找到AC该题12位中的CKSteven(他还未写该题博客),看了他的代码后才发现自己最开始的题意理解没错,是有一个bug(见代码第14行),不然能1A的。
这道题题意原文说的很清楚。我这里主要是定义了一个dist函数,来判断两个字符串的“距离”。相同距离就是0。然后如果短的字符串(设为a)不是长的字符串(设为b)的子串,距离就是inf,否则距离就是b的长度减a的长度,这个操作就是增加或删除字符尽量少的等价含义。
#include <algorithm> #include <iostream> #include <map> #include <string> using namespace std; const int inf = 10000; map<char, string> unit_code; map<string, string> dict; int dist(string a, string b) { if (a == b) return 0; if (b.size() < a.size()) swap(a, b); // a必须是b的子串 if (a != b.substr(0,a.size())) return inf; else return b.size()-a.size(); } string encode(string& s) { string ans; for (int i = 0; i < s.size(); i++) ans = ans + unit_code[s[i]]; return ans; } string decode(string s) { map<string, string>::iterator it = dict.begin(); string ans = it->first; int dis = dist(it->second, s); while (++it != dict.end()) { int d = dist(it->second, s); if (d < dis) ans = it->first, dis = d; else if (d == dis && d==0 && ans[ans.size()-1] != '!') ans = ans + "!"; } if (dis) ans = ans + "?"; return ans; } int main() { string s1, s2; while (cin >> s1, s1 != "*") { cin >> s2; unit_code[s1[0]] = s2; } while (cin >> s1, s1 != "*") dict[s1] = encode(s1); while (cin >> s1 && s1 != "*") cout << decode(s1) << "\n"; return 0; }
3.7 UVa509--RAID!
这题注意input给的数据是题目用的讲解图的转置,即每行代表一个磁盘disk。下面用的行列均指input标准输入的数组。
我构造了一个RAID类,用一个bool类型的大数组data存储0、1正文信息,整数n代表存了多少位。然后关键是Set成员函数的实现。
Set函数尝试将数据读入与恢复,并存储在data中,如果失败则返回0,否则返回1。Set函数的算法和步骤是本题的核心:
我构造了一个RAID类,用一个bool类型的大数组data存储0、1正文信息,整数n代表存了多少位。然后关键是Set成员函数的实现。
Set函数尝试将数据读入与恢复,并存储在data中,如果失败则返回0,否则返回1。Set函数的算法和步骤是本题的核心:
- 它先统计每列的‘x‘个数,已有的01也先做异或运算。
- 然后做列检验,如果该列没有‘x‘,则异或结果错误的return 0;如果该列有一个‘x‘,直接对其进行恢复;如果有多个‘x‘的,肯定有正文信息损坏且无法恢复,故也return 0。
- step 2若能顺利执行,进入该步,则此时str中一定不存在‘x‘。用技巧来依次读取正文即可。
#include <cstdio> #include <iostream> using namespace std; struct RAID { int n; bool data[64*100*6+10]; int Set(int d, int s, int b) { bool parity = 0; char str[7][6410], str1[10]; // 读入数据 scanf("%s", str1); if (str1[0]=='O') parity = 1; for (int i = 0; i < d; i++) scanf("%s", str[i]); // 统计每列有多少个x, 并统计已有01的异或值 int cnt[6410] = {}, i, j; bool check[6410] = {}; for (i = 0; i < d; i++) for (j = 0; j < s*b; j++) { if (str[i][j]=='x') cnt[j]++; else check[j] ^= (str[i][j]-'0'); } for (j = 0; j < s*b; j++) { // 每列校验 if (cnt[j] > 1) return 0; if (cnt[j] == 0 && check[j] != parity) return 0; // 否则只有一个x,直接恢复 for (i = 0; i < d; i++) if (str[i][j]=='x') { str[i][j] = (parity^check[j]) + '0'; break; } } // 生成数据data,遇到'x'时return 0 for (n = j = 0; j < s*b; j += s) for (i = 0; i < d; i++) { if (i == (j/s)%d) continue; // 注意此处技巧,巧妙的跳过校验码 for (int k = 0; k < s; k++) data[n++] = str[i][j+k]-'0'; } while (n%4 != 0) data[n++] = 0; return 1; } void out() { for (int i = 0; i < n; i += 4) printf("%X", data[i]*8 + data[i+1]*4 + data[i+2]*2 + data[i+3]); putchar('\n'); } }; int main() { int d, s, b, kase = 0; RAID A; while (cin >> d >> s >> b) { if (A.Set(d, s, b)) { printf("Disk set %d is valid, contents are: ", ++kase); A.out(); } else printf("Disk set %d is invalid.\n", ++kase); } return 0; }
3.8 UVa12108--Extraordinarily Tired Students
首先不要把这题想复杂了,题目给的数量都非常小,就是一道暴力模拟题。如果仍然不放心,比赛的时候根据现场AC情况也能猜出迭代上限不会太大。
保险的话,上限可以设100万(笔者实验过,5000就能AC该题了)。
保险的话,上限可以设100万(笔者实验过,5000就能AC该题了)。
#include <algorithm> #include <cstdio> using namespace std; struct Stu { int n, cnt; // n:总人数, cnt:当前睡觉人数 int a[10], b[10], c[10]; Stu(int _n): n(_n) { for (int i = cnt = 0; i < n; i++) { scanf("%d%d%d", a+i, b+i, c+i); if (c[i]>a[i]) cnt++; } } int Lim() { // 返回迭代上限 long long lim = 1; for (int i = 0; i < n; i++) lim *= a[i]+b[i]; return min(lim+5, 10000LL); } int Next() { // 返回当前睡觉总人数 int CanSleep = 0; if (cnt > n - cnt) CanSleep = 1; for (int i = 0; i < n; i++) { if (c[i]==a[i]+b[i]) cnt--, c[i] = 0; else if (c[i]==a[i]) { if (CanSleep) cnt++; else c[i] = 0; } c[i]++; } return cnt; } }; int main() { int n, kase = 0; while (scanf("%d", &n), n) { Stu A(n); int lim = A.Lim(), t = 2; while (A.Next() && t < lim) t++; printf("Case %d: %d\n", ++kase, t==lim?-1:t); } return 0; }我这里把迭代次数优化了下,有些多此一举把代码写的复杂了些,与时俱进yhj这份写的比较简洁。
3.9 UVa1591--Data Mining
花絮
首先,要耐心读懂题意,题目并不是特别复杂,不过是有一些专业背景而已。
然后想到,从实际角度考虑,A和B的值都应该不会太大才对。再看看数据规模,Pofs的值能达到2^20*2^10=2^30。故用64位整型存储,我们最多也只能左移30多位。
不过从这里就感觉题目不太对劲,出题人应该说明A、B的范围才对,难道是故意挖坑,让ACMer自己猜?这种坑也不是没见过,今年上海邀请赛,输出方程表达式的那题,题目就没有说系数为1的要忽略这一点(如1x+3y-1z-4应该输出x+3y-z-4),而是让选手自己从常识角度理解,把我们队可坑惨了。
与其乱猜,不如把测试数据翻出来:NEERC2003数据, 找到测试数据的输入输出,发现果然所有的A、B值都在0~31以内。
求解
然后这题就逗比了,只要暴力所有A、B的组合,看解是不是可行的并找出最优解。
首先,要耐心读懂题意,题目并不是特别复杂,不过是有一些专业背景而已。
然后想到,从实际角度考虑,A和B的值都应该不会太大才对。再看看数据规模,Pofs的值能达到2^20*2^10=2^30。故用64位整型存储,我们最多也只能左移30多位。
不过从这里就感觉题目不太对劲,出题人应该说明A、B的范围才对,难道是故意挖坑,让ACMer自己猜?这种坑也不是没见过,今年上海邀请赛,输出方程表达式的那题,题目就没有说系数为1的要忽略这一点(如1x+3y-1z-4应该输出x+3y-z-4),而是让选手自己从常识角度理解,把我们队可坑惨了。
与其乱猜,不如把测试数据翻出来:NEERC2003数据, 找到测试数据的输入输出,发现果然所有的A、B值都在0~31以内。
求解
然后这题就逗比了,只要暴力所有A、B的组合,看解是不是可行的并找出最优解。
(设有n个元素,每个P占x个字节,每个Q占y个字节。)
怎么判断一个解是否可行呢?想想如果Q连续存储,至少也得消耗n*y个字节,一个AB方案,如果算出的字节小于该值就是不可行的。否则这个内存越小,解就越优,依据这个来更新最优解ansA,ansB,ansN。
#include <iostream> using namespace std; int main() { long long n, x, y, N, A, B, ansN, ansA, ansB; while (cin >> n >> x >> y) { ansN = n*y<<10; for (A = 0; A < 32; A++) { for (B = 0; B < 32; B++) { N = (((n-1)*x+((n-1)*x<<A))>>B) + y; if (N>=n*y && N<ansN) { ansA = A; ansB = B; ansN = N; } } } cout << ansN << " " << ansA << " " << ansB << "\n"; } return 0; }
3.10 UVa815--Flooded!
以样例数据来解释解题思路。如下图所示:
- 可以把n*m块地的海拔高度a[i],按从小到大排成一列。
- 然后对输入的洪水体积,先除以100,这样一来,题目中的所有数据就统一了数量单位。如样例的洪水体积为10000,转化为高度就是sum=100。
- 接着设一个偏移量k,代表洪水从左往右淹到第k块地时停止。算法为k从1开始递增遍历,洪水每路过一块地,就把地的高度加入sum,退出条件就是洪水的平均高度不大于下一块地:sum/k < a[k+1]。
#include <algorithm> #include <cstdio> using namespace std; #define rep(i,a,b) for(int i=a; i<b; i++) const int maxn = 35; int a[maxn*maxn], n, m; int main() { for (int kase = 1; scanf("%d%d", &n, &m), n&&m; kase++) { n *= m; rep(i, 0, n) scanf("%d", a+i); sort(a, a+n); a[n] = 0x7fffffff; int k; double sum; scanf("%lf", &sum); sum /= 100.0; for (k = 1; k <= n; k++) { sum += a[k-1]; if (sum/k <= a[k]) break; } printf("Region %d\n", kase); printf("Water level is %.2lf meters.\n", sum/k); printf("%.2lf percent of the region is under water.\n\n", k*100.0/n); } return 0; }LRJ虽然建议多想几种方法,不过我想不到比这效率更优或代码更简单的思路。也就不乱凑方法了。
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。