题目76:最大效益
明明的爸爸开了一家小公司,公司里有5名职员。今天,公司接待了5位客户。明明的爸爸知道,和任何一位客户谈判并签下合同都要花一整天的时间,而他又希望在一天之内,和这5位客户都签好合同。因此,明明的爸爸要求公司里的5名职员分别与1位客户谈判。
明明的爸爸也知道,这5名职员和5位客户的性格各不相同。因此,不同的职员与不同的客户谈判,会给公司带来不同的经济效益。他现在要做出一个决策,让5名职员分别与哪位客户谈判,才能让公司今天的总经济效益最大。
明明的爸爸首先做了一张5行5列的效益表,如下所示:
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
在这张效益表中,每行代表一名公司职员,每列代表一个客户,每行中的5个数字就表示了当该行所代表的公司职员和每位客户谈判时所能产生的效益。明明的爸爸就要通过这张效益表来决定哪位职员与哪位顾客谈判,然后能够使公司的效益最大。就拿上面这张表来看,由于无论哪位职员与哪位客户谈判,所产生的效益都是1,因此最大的效益就是5。这是最简单的一种情况,但是当效益表里的数字变得复杂,就很难进行选择,到底哪种组合方式才是最优的。因此明明的爸爸求助于你,帮助他解决这个问题。
明明的爸爸的问题可以归结为:给你一张5行5列的效益表,表中的数字均为大于等于0的整数,要求在这张表中选出5个数字,使这5个数字的和最大。(注:这5个数字分别来自表中的不同行不同列,即同一行只能选择一个数字,同一列也只能选择一个数字。)
你写的程序要求从标准输入设备中读入测试数据作为你所写程序的输入数据。标准输入设备中有多组测试数据。每组测试数据占5行;每行包含5个正整数;第i行的第j个正整数Aij代表第i名职员与第j位客户谈判能为公司带来的经济效益(0≤Aij≤100, 1≤i,j≤5)。每组测试数据与其后一组测试数据之间没有任何空行;第一组测试数据前面以及最后一组测试数据后面也都没有任何空行。
对于每一组测试数据,你写的程序要求计算出一组相应的运算结果,并将每组运算结果作为你所写程序的输出数据依次写入到标准输出设备中。每组运算结果为一个整数s,即这一天中公司的最大可能总经济效益。例如:当测试数据中的所有Aij(1≤i,j≤5)均为1时,运算结果s应为5。输出时,每组运算结果s单独占一行,其行首和行尾都没有任何空格或其他任何字符;每组运算结果与其后一组运算结果之间没有任何空行或其他任何字符,第一组运算结果前面以及最后一组运算结果后面也都没有任何空行或其他任何字符。
注:通常,显示屏为标准输出设备。
总结:
-
table[5][5]:存储输入的效益表,used[5]:标记某列(客户)是否已被指派,max_sum:记录当前找到的最大总效益
-
按行进行搜索,从第0行开始,为每一行选择一个未被使用的列,每次选择后标记该列为已使用,递归处理下一行,当处理完第4行(5行全部处理完)时,得到一个完整的指派方案,更新最大总效益
-
estimate_max(row)函数:计算从当前行开始,剩余行理论上可能达到的最大效益,对于剩余每一行,取该行中所有未被使用的列的最大值,将这些最大值相加,如果当前和 + 剩余行最大可能值 <= 已知最大值,则停止搜索当前分支
cpp
#include <stdio.h>
#include <limits.h>
#define N 5
int table[N][N];
int used[N];
int max_sum;
// 剪枝函数:计算从第 row 行开始剩余行的最大可能收益
int estimate_max(int row) {
int est = 0;
for (int r = row; r < N; r++) {
int row_max = 0;
for (int c = 0; c < N; c++) {
if (!used[c] && table[r][c] > row_max) {
row_max = table[r][c];
}
}
est += row_max;
}
return est;
}
void backtrack(int row, int current_sum) {
if (row == N) {
if (current_sum > max_sum) {
max_sum = current_sum;
}
return;
}
// 剪枝:当前和 + 剩余行最大可能值 <= 已知最大值,则剪枝
if (current_sum + estimate_max(row) <= max_sum) {
return;
}
for (int col = 0; col < N; col++) {
if (!used[col]) {
used[col] = 1;
backtrack(row + 1, current_sum + table[row][col]);
used[col] = 0;
}
}
}
int main() {
while (scanf("%d", &table[0][0]) != EOF) {
// 读取当前组 5×5 表格
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (i == 0 && j == 0) continue; // 第一个数已读
scanf("%d", &table[i][j]);
}
}
// 初始化
for (int i = 0; i < N; i++) used[i] = 0;
max_sum = 0;
backtrack(0, 0);
printf("%d\n", max_sum);
}
return 0;
}
题目79:阵列
明明在上学的时候,参加数学兴趣班。在班上,老师介绍了一种非常有趣的阵列。
该阵列由n个正整数构成,阵列中的数字从1开始递增,数字的排序规则是从1开始由中间逆时针向外转出,2出现在1的下面,然后直至输出n为止。
例如当n=5的时候,阵列如下:
5
1 4
2 3
当n=9时,阵列如下:
7 6 5
8 1 4
9 2 3
当n=10时,阵列如下:
7 6 5
8 1 4
9 2 3
10
明明回家后想自己动手构造这样的阵列。他从n=1开始构造,但是他发现当n越来越大时,阵列的复杂性就越高,然后构造出来的阵列就越容易出错。为了降低构造阵列的出错率,提高构造速度,明明就求助于你,请你帮他写一个程序,来构造这样的阵列。
明明的问题可以归结为:给你一个正整数n,请你按题目描述中所述的方法,构造出阵列。
你写的程序要求从标准输入设备中读入测试数据作为你所写程序的输入数据。标准输入设备中有多组测试数据,每组测试数据仅占一行,每行仅有一个正整数n(1≤n≤99),即所要构造的阵列的大小。每组测试数据与其后一组测试数据之间没有任何空行,第一组测试数据前面以及最后一组测试数据后面也都没有任何空行。
对于每一组测试数据,你写的程序要求计算出一组相应的运算结果,并将这一组运算结果作为你所写程序的输出数据依次写入到标准输出设备中。每组运算结果为一个大小为n的阵列,阵列中的数字用一个空格隔开,具体形式请参考输出样例: 当n为个位数时,输出的每个数占1位,当n为两位数时,两位数所在的列输出的每个数占2位(不足2位的左边补空格)。 每组运算结果与其后一组运算结果之间有一个空行,最后一组运算结果之后没有任何空行。 注:通常,显示屏为标准输出设备。
总结:
n = 99时出错
-
根据n的范围预先确定最小能容纳所有数字的网格尺寸
-
数字1的位置根据n的大小有所不同:n ≤ 5:1在第2行第1列(0-based索引),n = 6-9:1在网格中心(1,1),n = 10-12:仍在(1,1),n > 12:1在网格中心偏左位置
-
从位置(r,c)开始,放入数字1,当前方向为"下",当前步长=1,已走步数=0;从2遍历到 n,尝试沿当前方向移动一步,如果新位置越界或已有数字,则逆时针旋转90度,在新位置放置当前数字,已走步数加1;如果已走步数达到当前步长,则改变方向,重置步数计数器,每改变两次方向后,步长增加1
-
填充完成后,扫描整个数组,找出有数字的最小行 min_r 和最大行 max_r,最小列 min_c 和最大列 max_c,只输出实际使用的区域
-
逐行输出,每行只输出到最后一个有数字的列,列间用1个空格分隔,空位置用空格填充以保持列对齐,如果一列中包含至少一个两位数,则该列所有数字都按2位宽度输出,否则按1位宽度输出
cpp
#include <stdio.h>
#include <string.h>
int main() {
int n;
int first = 1;
while (scanf("%d", &n) == 1) {
if (!first) {
printf("\n");
}
first = 0;
// 确定网格大小
int rows, cols;
if (n == 1) {
rows = cols = 1;
} else if (n <= 4) {
rows = 2;
cols = 2;
} else if (n <= 9) {
rows = cols = 3;
} else if (n <= 12) {
rows = 4;
cols = 3;
} else if (n <= 16) {
rows = cols = 4;
} else if (n <= 20) {
rows = 5;
cols = 4;
} else if (n <= 25) {
rows = cols = 5;
} else if (n <= 30) {
rows = 6;
cols = 5;
} else if (n <= 36) {
rows = cols = 6;
} else if (n <= 42) {
rows = 7;
cols = 6;
} else if (n <= 49) {
rows = cols = 7;
} else if (n <= 56) {
rows = 8;
cols = 7;
} else if (n <= 64) {
rows = cols = 8;
} else if (n <= 72) {
rows = 9;
cols = 8;
} else if (n <= 81) {
rows = cols = 9;
} else if (n <= 90) {
rows = 10;
cols = 9;
} else { // n <= 99
rows = cols = 10;
}
int arr[15][15];
memset(arr, 0, sizeof(arr));
// 1 的初始位置
int r, c;
if (n == 1) {
r = c = 0;
} else if (n <= 5) {
r = 1; c = 0; // 1在(1,0)
} else if (n <= 9) {
r = 1; c = 1;
} else if (n <= 12) {
r = 1; c = 1;
} else {
r = rows / 2;
c = (cols - 1) / 2;
}
arr[r][c] = 1;
// 方向:下、右、上、左
int dir[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
int step_len = 1;
int d = 0;
int num = 2;
int step_count = 0;
int dir_count = 0;
while (num <= n) {
int nr = r + dir[d][0];
int nc = c + dir[d][1];
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols || arr[nr][nc] != 0) {
d = (d + 1) % 4;
nr = r + dir[d][0];
nc = c + dir[d][1];
step_count = 0;
dir_count++;
if (dir_count % 2 == 0) {
step_len++;
}
}
r = nr;
c = nc;
arr[r][c] = num;
num++;
step_count++;
if (step_count >= step_len) {
d = (d + 1) % 4;
step_count = 0;
dir_count++;
if (dir_count % 2 == 0) {
step_len++;
}
}
}
// 确定实际使用的行列范围
int min_r = rows, max_r = -1, min_c = cols, max_c = -1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (arr[i][j] != 0) {
if (i < min_r) min_r = i;
if (i > max_r) max_r = i;
if (j < min_c) min_c = j;
if (j > max_c) max_c = j;
}
}
}
// 确定每列的宽度(1位或2位)
int col_width[15] = {0};
for (int j = min_c; j <= max_c; j++) {
col_width[j] = 1; // 默认1位
for (int i = min_r; i <= max_r; i++) {
if (arr[i][j] >= 10) {
col_width[j] = 2; // 这一列有两位数
break;
}
}
}
// 输出
for (int i = min_r; i <= max_r; i++) {
// 找到这一行最后一个有数字的列
int last_col = -1;
for (int j = max_c; j >= min_c; j--) {
if (arr[i][j] != 0) {
last_col = j;
break;
}
}
if (last_col == -1) continue; // 这一行全空
for (int j = min_c; j <= last_col; j++) {
if (j > min_c) {
printf(" "); // 列之间1个空格
}
if (arr[i][j] != 0) {
if (col_width[j] == 2) {
printf("%2d", arr[i][j]);
} else {
printf("%d", arr[i][j]);
}
} else {
// 空位置用空格填充,保持对齐
if (col_width[j] == 2) {
printf(" ");
} else {
printf(" ");
}
}
}
printf("\n");
}
}
return 0;
}
题目80:饲料调配
农夫约翰从来只用调配得最好的饲料来为他的奶牛。
饲料用三种原料调配成:大麦,燕麦和小麦。他知道自己的饲料精确的配比,在市场上是买不到这样的饲料的。他只好购买其他三种混合饲料(同样都由三种麦子组成),然后将它们混合,来调配他的完美饲料。
给出三组整数,表示 大麦:燕麦:小麦 的比例,找出用这三种饲料调配 x:y:z 的饲料的方法。
例如,给出目标饲料 3:4:5 和三种饲料的比例:
1:2:3
3:7:1
2:1:2
你必须编程找出使这三种饲料用量最少的方案,要是不能用这三种饲料调配目标饲料,输出'NONE'。'用量最少'意味着三种饲料的用量(整数)的和必须最小。
对于上面的例子,你可以用8份饲料1,2份饲料2,和5份饲料3,来得到7份目标饲料: 8*(1:2:3) + 1*(3:7:1) + 5*(2:1:2) = (21:28:35) = 7*(3:4:5)
以上数字中,表示饲料比例的整数都是小于100(数量级)的非负整数,表示各种饲料的份数的整数都小于100。一种混合物的比例不会由其他混合物的比例直接相加得到。
输入说明:Line 1: 三个用空格分开的整数,表示目标饲料
Line 2..4: 每行包括三个用空格分开的整数,表示农夫约翰买进的饲料的比例
输出文件要包括一行,这一行要么有四个整数,要么是'NONE'。前三个整数表示三种饲料的份数,用这样的配比可以得到目标饲料。第四个整数表示混合前三种饲料后得到的目标饲料的份数。
总结:
目标饲料比例为 x:y:z,三种饲料的比例分别为 (a1, b1, c1), (a2, b2, c2), (a3, b3, c3)。
设三种饲料的份数分别为 t, u, v,混合后得到的目标饲料份数为 w,则需满足:
ta1 + ua2 + va3 = wx,tb1 + ub2 + vb3 = wy,tc1 + uc2 + vc3 = wz
其中 t, u, v, w 都是非负整数,且 t+u+v 最小, t, u, v < 100,w 是混合后目标饲料的份数,由于比例是小于100的非负整数,遍历 t, u, v 从0到99,计算 w,并检查是否满足比例关系且 w 是正整数。
具体步骤:
-
遍历 t, u, v 从0到99
-
计算总大麦 = ta1 + ua2 + va3,总燕麦 = tb1 + ub2 + vb3,总小麦 = tc1 + uc2 + v*c3。
-
如果总大麦、总燕麦、总小麦均为0,则跳过
-
检查总大麦:总燕麦:总小麦是否与目标比例 x:y:z 成比例。即存在正整数 w 使得总大麦 = wx,总燕麦 = wy,总小麦 = w*z
-
如果成比例,则计算 w = 总大麦 / x(需确保整除,且 y、z 也相应整除)
-
记录满足条件的 t, u, v, w 中 t+u+v 最小的解。如果有多个解 t+u+v 相同,按遍历顺序取第一个
-
如果找到解,输出 t, u, v, w;否则输出 "NONE"
cpp
#include <stdio.h>
int main() {
int x, y, z;
scanf("%d %d %d", &x, &y, &z);
int a[3], b[3], c[3];
for (int i = 0; i < 3; i++) {
scanf("%d %d %d", &a[i], &b[i], &c[i]);
}
int min_sum = 300; // 由于 t, u, v 都小于 100,最大和为 297,初始设为较大值
int best_t = -1, best_u = -1, best_v = -1, best_w = -1;
for (int t = 0; t < 100; t++) {
for (int u = 0; u < 100; u++) {
for (int v = 0; v < 100; v++) {
int total_a = t * a[0] + u * a[1] + v * a[2];
int total_b = t * b[0] + u * b[1] + v * b[2];
int total_c = t * c[0] + u * c[1] + v * c[2];
// 计算 w,通过非零的比例项
int w = -1;
if (x != 0 && total_a % x == 0) {
w = total_a / x;
} else if (y != 0 && total_b % y == 0) {
w = total_b / y;
} else if (z != 0 && total_c % z == 0) {
w = total_c / z;
} else {
// 如果所有目标比例都为零,跳过
continue;
}
if (w <= 0) continue; // w 必须是正整数
// 检查比例是否匹配
if (total_a == w * x && total_b == w * y && total_c == w * z) {
int sum = t + u + v;
if (sum < min_sum) {
min_sum = sum;
best_t = t;
best_u = u;
best_v = v;
best_w = w;
}
}
}
}
}
if (best_t == -1) {
printf("NONE\n");
} else {
printf("%d %d %d %d\n", best_t, best_u, best_v, best_w);
}
return 0;
}



翻译:
电脑将完全按照编程的方式解决问题,无视效率,可替代的方法,可能的捷径或代码中可能的错误。学习和适应的计算机程序是人工智能和机器学习新兴领域的一部分。以人工智能为基础的产品分成两个主要的种类:基于规则的系统和模式识别系统。基于规则的系统尝试展示人类专家用的规则并且开发成本往往很高。基于模式的系统用关于一个问题的数据得出结论。基于模式的例子包括声音识别,字体识别,翻译和在线交易的新兴领域。
B部分
什么是计算机科学
I. 介绍
计算机科学是对理论,实验和工程的研究,这些是计算机设计和使用的基础,计算机是自动处理信息的设备。计算机科学的起源可以追溯到英国数学家查尔斯·巴贝奇的工作,他于1837年首次提出可编程的机器计算。直到二十世纪四十年代电子数字计算机的出现,计算机科学通常没有与数学与工程学分开。从那时开始,计算机涌现出了许多该学科独有的研究分支。
II. 计算机科学的发展
在二十世纪四十年代晚期到二十世界五十年代早期期间,计算机科学领域的早期工作聚焦于自动化科学和工程的计算过程呢。科学家和工程师开发计算的理论模型,使他们能够分析不同方法在执行各种计算时的效率。在这个期间,计算机科学与被称为数值分析的数学的分支有很大重叠,数值分析检验计算的准确性和精度。
单词:
