内容为武汉大学国家网络安全学院2022级大一第三学期“996”实训课程中所做的笔记,仅供个人复习使用,如有侵权请联系本人,将于15个工作日内将博客设置为仅粉丝可见。
目录
超级书架 2
- 时间限制:1000ms
- 内存限制:131072K
- 语言限制:C语言
Farmer John 最近为奶牛们的图书馆添置了一个巨大的书架,尽管它是如此的大,但它还是几乎瞬间就被各种各样的书塞满了。现在,只有书架的顶上还留有一点空间。
所有 N(1≤N≤20) 头奶牛都有一个确定的身高 H_i (1≤H_i≤1,000,000)。设所有奶牛身高的和为 S。书架的高度为 B,并且保证 1≤B≤S。
为了够到比最高的那头奶牛还要高的书架顶,奶牛们不得不象演杂技一般,一头站在另一头的背上,叠成一座“奶牛塔”。当然,这个塔的高度,就是塔中所有奶牛的身高之和。为了往书架顶上放东西,所有奶牛的身高和必须不小于书架的高度。
塔叠得越高便越不稳定,于是奶牛们希望找到一种方案,使得叠出的塔在高度不小于书架高度的情况下,高度尽可能小。你也可以猜到你的任务了:写一个程序,计算奶牛们叠成的塔在满足要求的情况下,最少要比书架高多少。
输入格式
第 1 行: 2 个用空格隔开的整数:N 和 B。
第2..N+1 行: 第 i+1 行是 1 个整数:H_i。
输出格式
输出 1 个非负整数,即奶牛们叠成的塔最少比书架高的高度。
样例解释
我们选用奶牛 1、3、4、5 叠成塔,她们的总高度为 3+3+5+6=17。任何方案都无法叠出高度为 16 的塔,于是答案为 11。
格式说明
输出时每行末尾的多余空格,不影响答案正确性
样例输入
5 16
3
1
3
5
6
样例输出
1
题解:这道题目就是枚举组合的经典应用了,通过枚举所有可能的堆叠情况,就能够确定最少奶牛塔比书架高多少。
我的答案
#include <stdio.h>
#include <stdlib.h>
int n, b, ans = 0x3f3f3f3f;
int h[25];
void dfs(int x, int sum) {
if (sum >= b) {
ans = ans < sum ? ans : sum;
return;
}
if (x > n) return;
dfs(x + 1, sum);
dfs(x + 1, sum + h[x]);
}
int main() {
scanf("%d%d", &n, &b);
for (int i = 1; i <= n; i++) scanf("%d", &h[i]);
dfs(1, 0);
printf("%d\n", ans - b);
return 0;
}
正方形
- 时间限制:1000ms
- 内存限制:65536K
- 语言限制:C语言
蒜头君手上有一些小木棍,它们长短不一,蒜头君想用这些木棍拼出一个正方形,并且每根木棍都要用到。 例如,蒜头君手上有长度为 1,2,3,3, 3 的 5 根木棍,他可以让长度为1,2 的木棍组成一条边,另外三根分别组成 3 条边,拼成一个边长为 3 的正方形。蒜头君希望你提前告诉他能不能拼出来,免得白费功夫。
输入格式
首先输入一个整数 n(4≤n≤20),表示木棍数量,接下来输入 n 根木棍的长度 p_i(1≤p_i≤10000)。
输出格式
如果蒜头君能拼出正方形,输出"Yes"
,否则输出"No"
。
样例输入1
4
1 1 1 1
样例输出1
Yes
样例输入2
5
10 20 30 40 50
样例输出2
No
我的答案
#include <stdio.h>
#include <stdlib.h>
int n, sum, len;
int p[25];
int vis[25];
int cmp(const void *a, const void *b) {
return *(int *)b - *(int *)a;
}
int dfs(int x, int now, int cnt) {
if (cnt == 3) return 1;
if (now == len) return dfs(1, 0, cnt + 1);
for (int i = x; i <= n; i++) {
if (vis[i]) continue;
if (now + p[i] > len) continue;
vis[i] = 1;
if (dfs(i + 1, now + p[i], cnt)) return 1;
vis[i] = 0;
if (now == 0) return 0;
int j = i;
while (j <= n && p[j] == p[i]) j++;
i = j - 1;
}
return 0;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &p[i]);
sum += p[i];
}
if (sum % 4 != 0) {
printf("No\n");
return 0;
}
len = sum / 4;
qsort(p + 1, n, sizeof(int), cmp);
if (dfs(1, 0, 0)) printf("Yes\n");
else printf("No\n");
return 0;
}
数独
- 时间限制:1000ms
- 内存限制:262144K
- 语言限制:C语言
蒜头君今天突然开始还念童年了,想回忆回忆童年。他记得自己小时候,有一个很火的游戏叫做数独。便开始来了一局紧张而又刺激的高阶数独。蒜头君做完发现没有正解,不知道对不对? 不知道聪明的你能否给出一个标准答案?
标准数独是由一个给与了提示数字的 9×9 网格组成,我们只需将其空格填上数字,使得每一行,每一列以及每一个 3×3 宫都没有重复的数字出现。
输入格式
一个 9×9 的数独,数字之间用空格隔开。*
表示需要填写的数字。
输出格式
输出一个 9×9 的数独,把出入中的*
替换成需要填写的数字即可。
特判说明
本题答案不唯一,符合要求的答案均正确
格式说明
输出时每行末尾的多余空格,不影响答案正确性
样例输入
* 2 6 * * * * * *
* * * 5 * 2 * * 4
* * * 1 * * * * 7
* 3 * * 2 * 1 8 *
* * * 3 * 9 * * *
* 5 4 * 1 * * 7 *
5 * * * * 1 * * *
6 * * 9 * 7 * * *
* * * * * * 7 5 *
样例输出
1 2 6 7 3 4 5 9 8
3 7 8 5 9 2 6 1 4
4 9 5 1 6 8 2 3 7
7 3 9 4 2 5 1 8 6
8 6 1 3 7 9 4 2 5
2 5 4 8 1 6 3 7 9
5 4 7 2 8 1 9 6 3
6 1 3 9 5 7 8 4 2
9 8 2 6 4 3 7 5 1
题解:这道题目类似八皇后问题,只不过八皇后是对每一行进行 1−8 的尝试,而这道题目是对每个空进行 1−9 的尝试。而且这道题目搜索到一种可行解就可以结束了。
标记方法为标记某行某个数字是否出现,标记某列某个数字是否出现,标记某个小方格某个数字是否出现。
我的答案
#include <stdio.h>
#include <stdbool.h>
#define N 9
int puzzle[N][N];
bool is_valid(int row, int col, int num) {
// 检查行
for (int i = 0; i < N; i++) {
if (puzzle[row][i] == num) {
return false;
}
}
// 检查列
for (int i = 0; i < N; i++) {
if (puzzle[i][col] == num) {
return false;
}
}
// 检查3x3宫格
int startRow = row - row % 3;
int startCol = col - col % 3;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (puzzle[i + startRow][j + startCol] == num) {
return false;
}
}
}
return true;
}
bool solve_sudoku() {
int row, col;
bool is_empty = true;
// 寻找空格
for (row = 0; row < N; row++) {
for (col = 0; col < N; col++) {
if (puzzle[row][col] == 0) {
is_empty = false;
break;
}
}
if (!is_empty) {
break;
}
}
// 如果没有空格,说明已经解决
if (is_empty) {
return true;
}
// 尝试填入数字1-9
for (int num = 1; num <= N; num++) {
if (is_valid(row, col, num)) {
puzzle[row][col] = num;
if (solve_sudoku()) {
return true;
} else {
puzzle[row][col] = 0;
}
}
}
return false;
}
int main() {
char c;
// 输入数独
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
scanf(" %c", &c);
if (c == '*') {
puzzle[i][j] = 0;
} else {
puzzle[i][j] = c - '0';
}
}
}
// 解决数独
solve_sudoku();
// 输出结果
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
printf("%d ", puzzle[i][j]);
}
printf("\n");
}
return 0;
}
鸣人和佐助
- 时间限制:1000ms
- 内存限制:65536K
- 语言限制:C语言
佐助被大蛇丸诱骗走了,鸣人在多少时间内能追上他呢?
已知一张地图(以二维矩阵的形式表示)以及佐助和鸣人的位置。地图上的每个位置都可以走到,只不过有些位置上有大蛇丸的手下,需要先打败大蛇丸的手下才能到这些位置。鸣人有一定数量的查克拉,每一个单位的查克拉可以打败一个大蛇丸的手下。假设鸣人可以往上下左右四个方向移动,每移动一个距离需要花费 1 个单位时间,打败大蛇丸的手下不需要时间。如果鸣人查克拉消耗完了,则只可以走到没有大蛇丸手下的位置,不可以再移动到有大蛇丸手下的位置。佐助在此期间不移动,大蛇丸的手下也不移动。请问,鸣人要追上佐助最少需要花费多少时间?
输入格式
输入的第一行包含三个整数:M,N,T。代表 M 行 N 列的地图和鸣人初始的查克拉数量 T。0<M,N<200,0≤T<10
后面是 M 行 N 列的地图,其中 @ 代表鸣人,+ 代表佐助。* 代表通路,# 代表大蛇丸的手下。
输出格式
输出包含一个整数 R,代表鸣人追上佐助最少需要花费的时间。如果鸣人无法追上佐助,则输出 −1。
格式说明
输出时每行末尾的多余空格,不影响答案正确性
样例输入1
4 4 1
#@##
**##
###+
****
样例输出1
6
样例输入2
4 4 2
#@##
**##
###+
****
样例输出2
4
题解:设状态 (x,y,t) 表示目前在 (x,y) 这个点,还剩 t 个查克拉的情况,起始状态为 (sx,sy,T) ,进行 BFS,注意判断这一次走过去需不需要消耗查克拉,第一次到达终点的步数就是所求解。
我的答案
#include <stdio.h>
#include <string.h>
#define MAXN 200
#define INF 0x3f3f3f3f
int M, N, T;
char map[MAXN][MAXN];
int vis[MAXN][MAXN][11];
int dir[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
int sx, sy, ex, ey, ans = INF;
void dfs(int x, int y, int t, int time) {
if (time >= ans) return;
if (x == ex && y == ey) {
ans = time;
return;
}
for (int i = 0; i < 4; i++) {
int nx = x + dir[i][0];
int ny = y + dir[i][1];
if (nx >= 0 && nx < M && ny >= 0 && ny < N) {
if (map[nx][ny] == '#' && t > 0 && time + 1 < vis[nx][ny][t - 1]) {
vis[nx][ny][t - 1] = time + 1;
dfs(nx, ny, t - 1, time + 1);
} else if (map[nx][ny] != '#' && time + 1 < vis[nx][ny][t]) {
vis[nx][ny][t] = time + 1;
dfs(nx, ny, t, time + 1);
}
}
}
}
int main() {
scanf("%d%d%d", &M, &N, &T);
for (int i = 0; i < M; i++) {
scanf("%s", map[i]);
for (int j = 0; j < N; j++) {
if (map[i][j] == '@') {
sx = i;
sy = j;
} else if (map[i][j] == '+') {
ex = i;
ey = j;
}
}
}
memset(vis, 0x3f, sizeof(vis));
vis[sx][sy][T] = 0;
dfs(sx, sy, T, 0);
if (ans == INF) printf("-1\n");
else printf("%d\n", ans);
return 0;
}
蒜头君回家
- 时间限制:2000ms
- 内存限制:131072K
- 语言限制:C语言
蒜头君要回家,但是他家的钥匙在他的朋友花椰妹手里,他要先从花椰妹手里取得钥匙才能回到家。花椰妹告诉他:“你家的钥匙被我复制了很多个,分别放在不同的地方。”
蒜头君希望能尽快回到家中,他首先需要取得任意一把钥匙,请你帮他计算出回家所需要的最短路程。
蒜头君生活的城市可以看做是一个 n×m 的网格,其中有道路有障碍,钥匙和家所在的地方可以看做是道路,可以通过。蒜头君可以在城市中沿着上下左右 4 个方向移动,移动一个格子算做走一步。
输入格式
第一行有两个整数 n,m。城市的地图是 n 行 m 列。(1≤n,m≤2000)
接下来的 n 行,每行 m 个字符,代表城市的地图。'.'
代表道路,'#'
代表障碍物,'S'
代表蒜头君所在的位置,'T'
代表蒜头家的位置,'P'
代表钥匙的位置。除了障碍物以外,别的地方都可以通过。题目保证蒜头君至少有一条路径可以顺利拿到钥匙并且回家。
输出格式
输出蒜头回家要走的最少步数,占一行。
格式说明
输出时每行末尾的多余空格,不影响答案正确性
样例输入
8 10
P.####.#P#
..#..#...#
..#T##.#.#
..........
..##.#####
..........
#####...##
###....S##
样例输出
21
任务提示:bfs 的时候标记数组多开一维度表示是否已经取得了钥匙的状态。如果到达终点并且取得钥匙的状态被标记,bfs 结束。
题解:这道题目是一个技巧题目,我们可以用同一个bfs
搜索两次。第一次从当前位置S
开始搜索到达所有点的最短路径,第二次从家T
开始搜索到达所有点的最短路径。最后我们计算每一个P
,计算出S
到P
加上T
到P
之和,这个代表找这把钥匙回家的最短路径。然后我们找出他们中最小的就可以了。
我的答案
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAXN 2005
typedef struct {
int x, y, step, key;
} Node;
int n, m;
char map[MAXN][MAXN];
int vis[MAXN][MAXN][2];
Node q[MAXN * MAXN];
int head, tail;
int sx, sy, tx, ty;
void bfs() {
head = tail = 0;
q[tail++] = (Node){sx, sy, 0, 0};
vis[sx][sy][0] = 1;
while (head < tail) {
Node u = q[head++];
if (u.x == tx && u.y == ty && u.key) {
printf("%d\n", u.step);
return;
}
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
if (abs(i) + abs(j) != 1) continue;
int nx = u.x + i;
int ny = u.y + j;
if (nx < 1 || nx > n || ny < 1 || ny > m || map[nx][ny] == '#' || vis[nx][ny][u.key]) continue;
vis[nx][ny][u.key] = 1;
if (map[nx][ny] == 'P') {
q[tail++] = (Node){nx, ny, u.step + 1, 1};
} else {
q[tail++] = (Node){nx, ny, u.step + 1, u.key};
}
}
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%s", map[i] + 1);
for (int j = 1; j <= m; j++) {
if (map[i][j] == 'S') {
sx = i;
sy = j;
} else if (map[i][j] == 'T') {
tx = i;
ty = j;
}
}
}
bfs();
return 0;
}