系统的整理下第四章的学习笔记。同上次一样,尽量在不依赖书本的情况下自己先把例题做出来。这次有许多道题代码量都比较大,在例题中我都用纯C语言编写,但由于习题的挑战性和复杂度,我最终还是决定在第五章开始前,就用C++来完成习题。不过所有的代码都是能在C++提交下AC的。
在习题中,我都习惯性的构造一个类来求解问题,从我个人角度讲,这会让我的思路清晰不少,希望自己的一些代码风格不会影响读者对解题思路的理解。
其实在第四章前,我就顾虑着是不是真的打算把题目全做了,这些题目代码量这么大,要耗费很长一段时间。事实证明我多虑了,一分付出一分收获,经过两周的练习,我的编码能力进步很大。以前比赛一直很怕复杂麻烦的模拟题,现在遇到模拟题反而很开心,因为我算法题会的很少啊~~~
我是从前往后花了两周把题目做一遍的,然后又从后往前整理出每道题的题解,再整理出博客。一些东西弄着弄着可能会没耐心了或心烦,忽悠了事,所以写的不好、有错的地方读者尽管指出,我会修改完善。
思路同书本。只要将两个字符串s1、s2的26个字母分别出现的次数统计出来,存在int a[26]与b[26],然后a、b数组按数值从小到大排序后,看a、b是否相等就能知道能否一一映射。
#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。
#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次全是错在交换这里。其实这题自己想出的解题方法还好,主要不是思路不合适的原因。
主要问题就是自己没有处理好查找问题,没想过:可不可能找不到?边查边修改值也是兵家之大忌,迫不得已这样操作时要万万小心。
#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。
#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的代码,竟然没有用结构体解!
#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等前面也可以编译通过)。
#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:
用两个数组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地址都相等时,退出循环。
#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的长度,这个操作就是增加或删除字符尽量少的等价含义。
#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函数的算法和步骤是本题的核心:
- 它先统计每列的'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该题了)。
#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的组合,看解是不是可行的并找出最优解。
(设有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;
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;
for(int i = 0; i < n; i++) 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虽然建议多想几种方法,不过我想不到比这效率更优或代码更简单的思路。也就不乱凑方法了。