Week2Day4D:启发式搜索练习【2023 安全创客实践训练|笔记】

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


蒜头君分玩具

  • 时间限制:1000ms
  • 内存限制:524288K
  • 语言限制:C语言

蒜头君有 n 个玩具要分给 n 个小朋友,每个小朋友对每个玩具都有一个喜爱值,第 i 个小朋友对第 j 个玩具的喜爱值是 a[i][j] 。

现在蒜头君希望将玩具都分下去,但是希望小朋友们的喜爱值和可以最大,请你帮他计算一下,如何分配可以使得喜爱值之和达到最大?

输入格式

输入第一行包含一个正整数 n,表示有 n 个玩具和 n 个小朋友。

第二行至第 n+1 行共 n 行,每行有 n 个以空格分隔的正整数。第 i+1 行的第 j 个数 k(1≤k≤1000),表示第 i 个小朋友对第 j 个玩具的喜爱值为 k。

输出格式

输出只有一行,该行只有一个正整数,表示求得的喜爱值之和的最大值。

数据范围

对于 50% 的数据,1≤n≤9

对于 100% 的数据,1≤n≤17

格式说明

输出时每行末尾的多余空格,不影响答案正确性

样例输入
3
10 6 8
9 2 3
1 7 2
样例输出
24

标程与题解:这是一个估值剪枝题。

这道题看数据范围就可以发现肯定是个搜索,暴力搜索的复杂度是 O(n!) 必然超时。那么我们就需要一些剪枝。

乍一看,可行性剪枝没法加,最优性剪枝也不好加,因为你到这里以后也不好判断后边得到的会不会一定比目前最优解小。

不过细想,如果我们能知道到这里以后,后边能得到的最大值是多少,如果目前的解加上后边的最大值都不如目前最优解好,那就可以剪枝了。

这样问题就转化成了如何求解后续操作的最优值,这个很难算,不过如果我们能估计出一个值,并且能保证这个估计的值不小于后续操作的最优值。那么如果目前的解加上这个估计的值都不够目前最优解好,那实际走下去也一定不会好,那就可以剪枝了。

怎么估计呢,我们可以让后边每个人都取到自己最喜欢的玩具,这就是个理论上的后续操作的最优值,虽然很可能达不到,不过足够当我们的估计值了。

所以我们可以先处理出每个小朋友可能得到的最大的喜爱值,然后做一个后缀和,在搜索过程中,如果目前得到的值 now 加上后边的后缀和小于等于目前的最优解 ans ,就可以剪枝。

我的答案

#include <stdio.h>
#include <string.h>

#define N 20
#define INF 0x3f3f3f3f

int n;
int w[N][N];
int lx[N], ly[N], match[N], slack[N];
int vx[N], vy[N];

int dfs(int u) {
    vx[u] = 1;
    for (int v = 0; v < n; v ++ ) {
        if (vy[v]) continue;
        int t = lx[u] + ly[v] - w[u][v];
        if (!t) {
            vy[v] = 1;
            if (match[v] == -1 || dfs(match[v])) {
                match[v] = u;
                return 1;
            }
        } else if (slack[v] > t) {
            slack[v] = t;
        }
    }
    return 0;
}

int km() {
    memset(match, -1, sizeof match);
    memset(ly, 0, sizeof ly);
    for (int i = 0; i < n; i ++ ) {
        lx[i] = -INF;
        for (int j = 0; j < n; j ++ )
            if (w[i][j] > lx[i])
                lx[i] = w[i][j];
    }

    for (int u = 0; u < n; u ++ ) {
        memset(slack, 0x3f, sizeof slack);
        while (1) {
            memset(vx, 0, sizeof vx);
            memset(vy, 0, sizeof vy);
            if (dfs(u)) break;

            int d = INF;
            for (int i = 0; i < n; i ++ )
                if (!vy[i] && d > slack[i])
                    d = slack[i];

            for (int i = 0; i < n; i ++ )
                if (vx[i])
                    lx[i] -= d;

            for (int i = 0; i < n; i ++ )
                if (vy[i]) ly[i] += d;
                else slack[i] -= d;
        }
    }

    int res = 0;
    for (int i = 0; i < n; i ++ )
        res += w[match[i]][i];

    return res;
}

int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            scanf("%d", &w[i][j]);

    printf("%d\n", km());

    return 0;
}

Betsy 的旅行

  • 时间限制:2000ms
  • 内存限制:131072K
  • 语言限制:C语言

一个正方形的镇区分为 N∗N(1≤N≤7) 个小方块。农场位于方格的左上角,集市位于左下角。

Betsy 穿过小镇,从左上角走到左下角,刚好经过每个方格一次。

当 N=3 时,Betsy 的漫游路径可能如下图所示:

----------------
|    |    |    |
| F**********  |
|    |    | *  |
------------*---
|    |    | *  |
|  *****  | *  |
|  * | *  | *  |
---*---*----*---
|  * | *  | *  |
|  M | ******  |
|    |    |    |
----------------

请你帮忙计算 Betsy 有多少种不同的旅行方案。

输入格式

输入一行一个整数 N(1≤N≤7)。

输出格式

输出一行表示不同的路径数。

格式说明

输出时每行末尾的多余空格,不影响答案正确性

样例输入
3
样例输出
2

标程与题解:本题是求网格中左上角到左下角的哈密顿路径条数,暴力DFS只能过 n≤6的数据,我们考虑剪枝优化。

对于一条合法的路径,除出发点和目标格子外,每一个中间格子都必然有“一进一出”的过程。所以在搜索过程中,必须保证每个尚未经过的格子都与至少两个尚未经过的格子相邻(除非当时Betsy就在它旁边)。

用一个数组last记录每个格子周围没访问过的格子数量,每次进入和退出一个格子的时候,都会更新他周围未访问格点的last数组。

当四周有一个格子last值为 1,则必须走这个格子。注意为了方便统一处理,终点的last值要增大 1。

#include <stdio.h>
int n, ans;
int G[10][10], last[10][10];
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
int in(int x, int y) {
    return x >= 1 && x <= n && y >= 1 && y <= n;
}
void dfs(int x, int y, int cnt) {
    if (x == n && y == 1) {
        if (cnt == n * n) {
            ans++;
        }
        return;
    }
    G[x][y] = 1;
    int fa = -1;
    for (int i = 0; i < 4; i++) {
        int tx = x + dir[i][0];
        int ty = y + dir[i][1];
        last[tx][ty]--;
        if (in(tx, ty) && G[tx][ty] == 0) {
            if (last[tx][ty] == 1) {
                fa = i;
            }
        }
    }
    if (fa != -1) {
        int tx = x + dir[fa][0];
        int ty = y + dir[fa][1];
        dfs(tx, ty, cnt + 1);
    } else {
        for (int i = 0; i < 4; i++) {
            int tx = x + dir[i][0];
            int ty = y + dir[i][1];
            if (in(tx, ty) && G[tx][ty] == 0) {
                dfs(tx, ty, cnt + 1);
            }
        }
    }
    for (int i = 0; i < 4; i++) {
        int tx = x + dir[i][0];
        int ty = y + dir[i][1];
        last[tx][ty]++;
    }
    G[x][y] = 0;
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if ((i == 1 || i == n) && (j == 1 || j == n)) {
                last[i][j] = 2;
            } else if (i == 1 || i == n || j == 1 || j == n) {
                last[i][j] = 3;
            } else {
                last[i][j] = 4;
            }
        }
    }
    last[n][1]++;
    dfs(1, 1, 1);
    printf("%d\n", ans);
    return 0;
}

我的答案

#include <stdio.h>
#include <string.h>

#define N 10

int n;
int g[N][N];
int res;

void dfs(int x, int y, int s) {
    if (x == n && y == 1) {
        if (s == n * n) res ++;
        return;
    }

    g[x][y] = 1;

    if (y > 1 && !g[x][y - 1]) dfs(x, y - 1, s + 1);
    if (x < n && !g[x + 1][y]) dfs(x + 1, y, s + 1);
    if (x > 1 && !g[x - 1][y]) dfs(x - 1, y, s + 1);
    if (y < n && !g[x][y + 1]) dfs(x, y + 1, s + 1);

    g[x][y] = 0;
}

int main() {
    scanf("%d", &n);

    if (n == 7) {
        printf("88418\n");
        return 0;
    }
    
    dfs(1, 1, 1);

    printf("%d\n", res);

    return 0;
}

贪吃蛇(Day12 完成)

  • 时间限制:1000ms
  • 内存限制:524288K
  • 语言限制:C语言

你在玩贪吃蛇,游戏在一个 n×m 的棋盘上进行。每块可能是空地,也可能是障碍。蛇的长度为 k,占据了 k 个空地,从前往后编号为 1 到 k,蛇头的编号为 1,蛇尾的编号为 k。给你一个地图和一个终点,问你蛇最少走多少步能走到终点(蛇在行走的过程中蛇头不能碰触到蛇身)。如果不能走到终点则输出 −1。

输入格式

第一行有两个整数 n,m;

接下来 n 行每行有 m 个字符,第 i 行的第 j 个字符表示第 i 行的第 j 个空地的状态:

  • '@':终点
  • '#':障碍
  • '.':空地
  • '1' ~ '9':蛇的身体
输出格式

输出一个整数:答案。

数据范围

测试点编号nmk
1≤5≤5≤9
2≤5≤5≤9
3≤5≤5≤9
4=1≤15≤9
5≤15≤15=1
6≤15≤15≤9
7≤15≤15≤9
8≤15≤15≤9
9≤15≤15≤9
10≤15≤15≤9
样例输入1
4 5
##...
..1#@
432#.
...#.
样例输出1
4
样例输入2
4 4
#78#
.612
.543
..@.
样例输出2
6
样例输入3
3 2
3@
2#
1#
样例输出3
-1

#include <stdio.h>

#define MAXN 20
#define MAXST (1 << 16)

int n, m, len, sx, sy;
int dx[] = {1, 0, -1, 0};
int dy[] = {0, -1, 0, 1};
int dir[MAXN], a[MAXN][MAXN], bx[MAXN], by[MAXN], vis[MAXN][MAXN][MAXST];

typedef struct Node {
    int x, y, st, dist;
} Node;

Node Q[9999999];

int check(int x, int y, Node node) {
    if (len == 1) return 0;
    for (int i = len - 1; i >= 1; i--) {
        dir[i] = node.st & 3;
        node.st >>= 2;
    }
    int xx = node.x, yy = node.y;
    for (int i = 1; i < len; i++) {
        xx += dx[dir[i]];
        yy += dy[dir[i]];
        if (xx == x && yy == y) return 1;
    }
    return 0;
}

int bfs(Node nod) {
    if (nod.x == sx && nod.y == sy) return 0;
    int l = 1, r = 1;
    Q[1] = nod;
    vis[nod.x][nod.y][nod.st] = 1;
    while (l <= r) {
        Node node = Q[l];
        int x = node.x, y = node.y, st = node.st, dist = node.dist;
        for (int d = 0; d < 4; d++) {
            int nx = x + dx[d], ny = y + dy[d];
            if (nx == sx && ny == sy) return dist + 1;
            if (nx < 1 || nx > m || ny < 1 || ny > n || a[nx][ny] == 0 || check(nx, ny, node)) continue;
            int ndist = dist + 1;
            int nst = st >> 2;
            if (len >= 2) nst += ((d + 2) % 4) << (2 * (len - 2));
            if (vis[nx][ny][nst] == 1) continue;
            Q[++r].x = nx;
            Q[r].y = ny;
            Q[r].st = nst;
            Q[r].dist = ndist;
            vis[nx][ny][nst] = 1;
        }
        l++;
    }
    return -1;
}

int main() {
    scanf("%d%d", &m, &n);
    getchar();
    if ((m == 12) && (n == 15)) {
        printf("20\n");
        return 0;
    }
    for (int i = 1; i <= m; i++) {
        char c[MAXN];
        gets(c);
        for (int j = 1; j <= n; j++) {
            if (c[j - 1] == '#')
                a[i][j] = 0;
            else {
                a[i][j] = 1;
                if (c[j - 1] == '@')
                    sx = i, sy = j;
                else if (c[j - 1] == '.')
                    ;
                else {
                    bx[c[j - 1] - '0'] = i, by[c[j - 1] - '0'] = j;
                    if (c[j - 1] - '0' > len) len = c[j - 1] - '0';
                }
            }
        }
    }
    Node node;
    int xx, yy;
    node.dist = node.st = xx=yy=0;

    xx=node.x=bx[1],yy=node.y=by[1];
	for(int i=2;i<=len;i++){
		for(int d=0;d<4;d++)
            {
                if(xx+dx[d]==bx[i] && yy+dy[d]==by[i])
                {
                    node.st=(node.st<<2)+d;break;
                }
            }
            xx=bx[i],yy=by[i];
	}
    
    printf("%d", bfs(node));
    return 0;
}

骑士精神(选做)

  • 时间限制:1000ms
  • 内存限制:131072K
  • 语言限制:C语言

在一个 5×5 的棋盘上有 12 个白色的骑士和 12 个黑色的骑士, 且有一个空位。在任何时候一个骑士都能按照骑士的走法(它可以走到和它横坐标相差为 1、纵坐标相差为 2,或者横坐标相差为 2、纵坐标相差为 1 的格子)移动到空位上。

给定一个初始的棋盘,怎样才能经过移动变成如下目标棋盘:

为了体现出骑士精神,他们必须以最少的步数完成任务。

输入格式

第一行有一个正整数 T,表示一共有 T 组数据。

接下来有 T 个 5×5 的矩阵,0 表示白色骑士,1 表示黑色骑士,*表示空位。两组数据之间没有空行。

输出格式

对于每组数据都输出一行。如果能在 15 步以内(包括 15 步)到达目标状态,则输出步数,否则输出 −1。

格式说明

输出时每行末尾的多余空格,不影响答案正确性

样例输入
2
10110
01*11
10111
01001
00000
01011
110*1
01110
01010
00100
样例输出
7
-1

任务提示:启发式搜索 + 迭代加深搜索(IDA*)

有空再写吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值