Week2Day3C:搜索进阶练习【2023 安全创客实践训练|笔记】

内容为武汉大学国家网络安全学院2022级大一第三学期“996”实训课程中所做的笔记,仅供个人复习使用,如有侵权请联系本人,将于15个工作日内将博客设置为仅粉丝可见。

目录

超级书架 2

输入格式

输出格式

样例解释

样例输入

样例输出

我的答案

正方形

输入格式

输出格式

样例输入1

样例输出1

样例输入2

样例输出2

我的答案

数独

输入格式

输出格式

样例输入

样例输出

我的答案

鸣人和佐助

输入格式

输出格式

样例输入1

样例输出1

样例输入2

样例输出2

我的答案

蒜头君回家

输入格式

输出格式

样例输入

样例输出

我的答案


超级书架 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,计算出SP加上TP之和,这个代表找这把钥匙回家的最短路径。然后我们找出他们中最小的就可以了。

我的答案

#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;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值