AcWing算法提高课 Level-3 第二章 搜索

在这里插入图片描述
在这里插入图片描述

  1. 池塘计数
    题目
    提交记录
    讨论
    题解
    视频讲解

农夫约翰有一片 N∗M 的矩形土地。

最近,由于降雨的原因,部分土地被水淹没了。

现在用一个字符矩阵来表示他的土地。

每个单元格内,如果包含雨水,则用”W”表示,如果不含雨水,则用”.”表示。

现在,约翰想知道他的土地中形成了多少片池塘。

每组相连的积水单元格集合可以看作是一片池塘。

每个单元格视为与其上、下、左、右、左上、右上、左下、右下八个邻近单元格相连。

请你输出共有多少片池塘,即矩阵中共有多少片相连的”W”块。

输入格式
第一行包含两个整数 N 和 M。

接下来 N 行,每行包含 M 个字符,字符为”W”或”.”,用以表示矩形土地的积水状况,字符之间没有空格。

输出格式
输出一个整数,表示池塘数目。

数据范围
1≤N,M≤1000
输入样例:
10 12
W…WW.
.WWW…WWW
…WW…WW.
…WW.
…W…
…W…W…
.W.W…WW.
W.W.W…W.
.W.W…W.
…W…W.
输出样例:
3

//连通一般分为四连通(上下左右)和八连通(只要有公共点)
#include <bits/stdc++.h>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1010, M = N * N;

int n, m;
char g[N][N];
PII q[M];
bool st[N][N];

void bfs(int sx, int sy)
{
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    st[sx][sy] = 1;

    while (hh <= tt)
    {
        PII t = q[hh ++ ];

        for (int i = t.x - 1; i <= t.x + 1; i ++ )
            for (int j = t.y - 1; j <= t.y + 1; j ++ )
            {
                if (i == t.x && j == t.y) continue;
                if (i < 0 || i >= n || j < 0 || j >= m) continue;
                if (g[i][j] == '.' || st[i][j]) continue;

                q[ ++ tt] = {i, j};
                st[i][j] = 1;
            }
    }
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i ++ ) scanf("%s", g[i]); //因为要读1e3*1e3的数据,所以最好用scanf

    int cnt = 0; 
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
        {
            if (g[i][j] == 'W' && !st[i][j])
            {
                bfs(i, j);
                cnt ++ ;
            }
        }

    cout << cnt << endl;

    return 0;
}



  1. 城堡问题
    题目
    提交记录
    讨论
    题解
    视频讲解
1   2   3   4   5   6   7  

#############################
1 # | # | # | | #
#####—#####—#---#####—#
2 # # | # # # # #
#—#####—#####—#####—#
3 # | | # # # # #
#—#########—#####—#---#
4 # # | | | | # #
#############################
(图 1)

= Wall

| = No wall

  • = No wall

方向:上北下南左西右东。
图1是一个城堡的地形图。

请你编写一个程序,计算城堡一共有多少房间,最大的房间有多大。

城堡被分割成 m∗n个方格区域,每个方格区域可以有0~4面墙。

注意:墙体厚度忽略不计。

输入格式
第一行包含两个整数 m 和 n,分别表示城堡南北方向的长度和东西方向的长度。

接下来 m 行,每行包含 n 个整数,每个整数都表示平面图对应位置的方块的墙的特征。

每个方块中墙的特征由数字 P 来描述,我们用1表示西墙,2表示北墙,4表示东墙,8表示南墙,P 为该方块包含墙的数字之和。

例如,如果一个方块的 P 为3,则 3 = 1 + 2,该方块包含西墙和北墙。

城堡的内墙被计算两次,方块(1,1)的南墙同时也是方块(2,1)的北墙。

输入的数据保证城堡至少有两个房间。

输出格式
共两行,第一行输出房间总数,第二行输出最大房间的面积(方块数)。

数据范围
1≤m,n≤50,
0≤P≤15
输入样例:
4 7
11 6 11 6 3 10 6
7 9 6 13 5 15 5
1 10 12 7 13 7 5
13 11 10 8 10 12 13
输出样例:
5
9
难度:简单
时/空限制:1s / 64MB
总通过数:2377
总尝试数:3236
来源:《信息学奥赛一本通》
算法标签

#include <bits/stdc++.h>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 55, M = N * N;

int n, m;
int g[N][N];
PII q[M];
bool st[N][N];

int bfs(int sx, int sy)
{
    int dx[] = {0, -1, 0, 1}, dy[] = {-1, 0, 1, 0};

    int hh = 0, tt = 0;
    int area = 0;

    q[0] = {sx, sy};
    st[sx][sy] = 1;

    while (hh <= tt)
    {
        PII t = q[hh ++ ];
        area ++ ;

        for (int i = 0; i < 4; i ++ )
        {
            int a = t.x + dx[i], b = t.y + dy[i];

            if (a < 0 || a >= n || b < 0 || b >= m) continue;
            if (st[a][b]) continue;
            // if (g[a][b] >> i & 1) continue; 是t.x而非a
            //1 2 4 8 -> 二进制中0 1 2 3位上是否为1
            if (g[t.x][t.y] >> i & 1) continue; //二进制中第i位是否为1,若为1则返回1

            st[a][b] = 1;
            // area ++ ; //不然的话main函数中。。。 整个area会少1
            q[ ++ tt] = {a, b};
        }
    }

    return area;
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            cin >> g[i][j];

    int cnt = 0, area = 0;

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
        {
            if (!st[i][j])
            {
                cnt ++ ;
                area = max(area, bfs(i, j));
            }
        }

    cout << cnt << endl;
    cout << area << endl;
    return 0;
}



  1. 山峰和山谷
    题目
    提交记录
    讨论
    题解
    视频讲解

FGD小朋友特别喜欢爬山,在爬山的时候他就在研究山峰和山谷。

为了能够对旅程有一个安排,他想知道山峰和山谷的数量。

给定一个地图,为FGD想要旅行的区域,地图被分为 n×n 的网格,每个格子 (i,j) 的高度 w(i,j) 是给定的。

若两个格子有公共顶点,那么它们就是相邻的格子,如与 (i,j) 相邻的格子有(i−1,j−1),(i−1,j),(i−1,j+1),(i,j−1),(i,j+1),(i+1,j−1),(i+1,j),(i+1,j+1)。

我们定义一个格子的集合 S 为山峰(山谷)当且仅当:

S 的所有格子都有相同的高度。
S 的所有格子都连通。
对于 s 属于 S,与 s 相邻的 s′ 不属于 S,都有 ws>ws′(山峰),或者 ws<ws′(山谷)。
如果周围不存在相邻区域,则同时将其视为山峰和山谷。
你的任务是,对于给定的地图,求出山峰和山谷的数量,如果所有格子都有相同的高度,那么整个地图即是山峰,又是山谷。

输入格式
第一行包含一个正整数 n,表示地图的大小。

接下来一个 n×n 的矩阵,表示地图上每个格子的高度 w。

输出格式
共一行,包含两个整数,表示山峰和山谷的数量。

数据范围
1≤n≤1000,
0≤w≤109
输入样例1:
5
8 8 8 7 7
7 7 8 8 7
7 7 7 7 7
7 8 8 7 8
7 8 8 8 8
输出样例1:
2 1
输入样例2:
5
5 7 8 3 1
5 5 7 6 6
6 6 6 2 8
5 7 2 5 8
7 1 0 1 7
输出样例2:
3 3
样例解释
样例1:

1.png

样例2:

2.png

难度:中等
时/空限制:1s / 64MB
总通过数:2400
总尝试数:4415
来源:《信息学奥赛一本通》 , POI2007
算法标签

#include <bits/stdc++.h>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1010, M = N * N;

int n;
int g[N][N];
PII q[M];
bool st[N][N];

void bfs(int sx, int sy, bool &has_higher, bool &has_lower) //&可以改变main函数调用的值
{
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    st[sx][sy] = 1;
    
    while (hh <= tt)
    {
        PII t = q[hh ++ ];

        for (int i = t.x - 1; i <= t.x + 1; i ++ )
            for (int j = t.y - 1; j <= t.y + 1; j ++ )
            {
                if (i == t.x && j == t.y) continue;
                if (i < 0 || i >= n || j < 0 || j >= n) continue;

                //如果高度不相等,不是连通块,且根据高度得知周围有比它高或者比它低判断是否山谷或是否山峰

                if (g[i][j] != g[t.x][t.y]) 
                {
                    if (g[i][j] < g[t.x][t.y]) has_lower = 1;
                    else has_higher = 1;
                }
                else if (!st[i][j])
                {
                    q[ ++ tt] = {i, j};
                    st[i][j] = 1;
                }
            }
    }
}

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

    int peak = 0, valley = 0;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
        {
            if (!st[i][j])
            {
                bool has_higher = 0, has_lower = 0;
                bfs(i, j, has_higher, has_lower);
                if (!has_higher) peak ++ ;
                if (!has_lower) valley ++ ; //不能使用else if,因为可能既是山谷也是山峰
            }
        }

    printf("%d %d\n", peak, valley);
}


  1. 迷宫问题
    题目
    提交记录
    讨论
    题解
    视频讲解

给定一个 n×n 的二维数组,如下所示:

int maze[5][5] = {

0, 1, 0, 0, 0,

0, 1, 0, 1, 0,

0, 0, 0, 0, 0,

0, 1, 1, 1, 0,

0, 0, 0, 1, 0,

};
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。

数据保证至少存在一条从左上角走到右下角的路径。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含 n 个整数 0 或 1,表示迷宫。

输出格式
输出从左上角到右下角的最短路线,如果答案不唯一,输出任意一条路径均可。

按顺序,每行输出一个路径中经过的单元格的坐标,左上角坐标为 (0,0),右下角坐标为 (n−1,n−1)。

数据范围
0≤n≤1000
输入样例:
5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
输出样例:
0 0
1 0
2 0
2 1
2 2
2 3
2 4
3 4
4 4

#include <bits/stdc++.h>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1010, M = N * N;

int n;
int g[N][N];
PII q[M];
PII pre[N][N];

void bfs(int sx, int sy)
{
    memset(pre, -1, sizeof(pre)); //初始化pre数组,定义一个不能到达的值为未访问

    int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};

    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    pre[sx][sy] = {0, 0};

    while (hh <= tt)
    {
        PII t = q[hh ++ ];

        for (int i = 0; i < 4; i ++ )
        {
            int a = t.x + dx[i], b = t.y + dy[i];

            if (a < 0 || a >= n || b < 0 || b >= n) continue;
            if (g[a][b]) continue;
            if (pre[a][b].x != -1) continue;   //不能写pre[a][b] != -1

            q[ ++ tt] = {a, b};
            pre[a][b] = t;
        }
    }
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            scanf("%d", &g[i][j]);
    
    bfs(n - 1, n - 1); //从终点开始遍历,为了pre

    PII end = {0, 0}; //定义一个pair类型变量end方便遍历pre数组

    while (1)
    {
        printf("%d %d\n", end.x, end.y);
        if (end.x == n - 1 && end.y == n - 1) break;
        end = pre[end.x][end.y];
    }

    return 0;
}



  1. 武士风度的牛
    题目
    提交记录
    讨论
    题解
    视频讲解

农民 John 有很多牛,他想交易其中一头被 Don 称为 The Knight 的牛。

这头牛有一个独一无二的超能力,在农场里像 Knight 一样地跳(就是我们熟悉的象棋中马的走法)。

虽然这头神奇的牛不能跳到树上和石头上,但是它可以在牧场上随意跳,我们把牧场用一个 x,y 的坐标图来表示。

这头神奇的牛像其它牛一样喜欢吃草,给你一张地图,上面标注了 The Knight 的开始位置,树、灌木、石头以及其它障碍的位置,除此之外还有一捆草。

现在你的任务是,确定 The Knight 要想吃到草,至少需要跳多少次。

The Knight 的位置用 K 来标记,障碍的位置用 * 来标记,草的位置用 H 来标记。

这里有一个地图的例子:

         11 | . . . . . . . . . .
         10 | . . . . * . . . . . 
          9 | . . . . . . . . . . 
          8 | . . . * . * . . . . 
          7 | . . . . . . . * . . 
          6 | . . * . . * . . . H 
          5 | * . . . . . . . . . 
          4 | . . . * . . . * . . 
          3 | . K . . . . . . . . 
          2 | . . . * . . . . . * 
          1 | . . * . . . . * . . 
          0 ----------------------
                                1 
            0 1 2 3 4 5 6 7 8 9 0 

The Knight 可以按照下图中的 A,B,C,D… 这条路径用 5 次跳到草的地方(有可能其它路线的长度也是 5):

         11 | . . . . . . . . . .
         10 | . . . . * . . . . .
          9 | . . . . . . . . . .
          8 | . . . * . * . . . .
          7 | . . . . . . . * . .
          6 | . . * . . * . . . F<
          5 | * . B . . . . . . .
          4 | . . . * C . . * E .
          3 | .>A . . . . D . . .
          2 | . . . * . . . . . *
          1 | . . * . . . . * . .
          0 ----------------------
                                1
            0 1 2 3 4 5 6 7 8 9 0

注意: 数据保证一定有解。

输入格式
第 1 行: 两个数,表示农场的列数 C 和行数 R。

第 2…R+1 行: 每行一个由 C 个字符组成的字符串,共同描绘出牧场地图。

输出格式
一个整数,表示跳跃的最小次数。

数据范围
1≤R,C≤150
输入样例:
10 11



.

…H


.K…
…*

输出样例:
5

#include <bits/stdc++.h>
#define endl '\n'
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 155, M = N * N;

int n, m;
char g[N][N];
int dist[N][N];
PII q[M];

int bfs()
{
    memset(dist, -1, sizeof(dist));

    int dx[] = {-2, -1, 1, 2, 2, 1, -1, -2}, dy[] = {1, 2, 2, 1, -1, -2, -2, -1};

    int sx, sy;

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            if (g[i][j] == 'K')
                sx = i, sy = j;

    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    dist[sx][sy] = 0;

    while (hh <= tt)
    {
        PII t = q[hh ++ ];

        for (int i = 0; i < 8; i ++ )
        {
            int a = t.x + dx[i], b = t.y + dy[i];

            if (a < 0 || a >= n || b < 0 || b >= m) continue;
            if (g[a][b] == '*') continue;
            if (dist[a][b] != -1) continue;
            if (g[a][b] == 'H') return dist[t.x][t.y] + 1;

            dist[a][b] = dist[t.x][t.y] + 1;
            q[ ++ tt] = {a, b};
        }
    }

    return -1;
}

int main()
{
    // cin >> n >> m;
    cin >> m >> n;
    for (int i = 0; i < n; i ++ ) cin >> g[i];

    cout << bfs() << endl;
}



  1. 抓住那头牛
    题目
    提交记录
    讨论
    题解
    视频讲解

农夫知道一头牛的位置,想要抓住它。

农夫和牛都位于数轴上,农夫起始位于点 N,牛位于点 K。

农夫有两种移动方式:

从 X 移动到 X−1 或 X+1,每次移动花费一分钟
从 X 移动到 2∗X,每次移动花费一分钟
假设牛没有意识到农夫的行动,站在原地不动。

农夫最少要花多少时间才能抓住牛?

输入格式
共一行,包含两个整数N和K。

输出格式
输出一个整数,表示抓到牛所花费的最少时间。

数据范围
0≤N,K≤105
输入样例:
5 17
输出样例:
4

//所有边的权重相同都为1,就可以用bfs求出最短路径
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10; //范围 wa

int n, k;
int q[N];
int dist[N];

int bfs()
{
    memset(dist, -1, sizeof dist);
    dist[n] = 0;
    q[0] = n;

    int hh = 0, tt = 0;

    while (hh <= tt)
    {
        int t = q[hh ++ ];

        if (t == k) return dist[k];

        if (t + 1 < N && dist[t + 1] == -1)
        {
            dist[t + 1] = dist[t] + 1;
            q[ ++ tt] = t + 1;
        }
        if (t - 1 >= 0 && dist[t - 1] == -1)
        {
            dist[t - 1] = dist[t] + 1;
            q[ ++ tt] = t - 1;
        }
        if (t * 2 < N && dist[t * 2] == -1)
        {
            dist[t * 2] = dist[t] + 1;
            q[ ++ tt] = t * 2;
        }
    }

    return -1;
}

int main()
{
    cin >> n >> k;

    cout << bfs() << endl;

    return 0;
}


  1. 矩阵距离
    题目
    提交记录
    讨论
    题解
    视频讲解

给定一个 N 行 M 列的 01 矩阵 A,A[i][j] 与 A[k][l] 之间的曼哈顿距离定义为:

dist(A[i][j],A[k][l])=|i−k|+|j−l|
输出一个 N 行 M 列的整数矩阵 B,其中:

B[i][j]=min1≤x≤N,1≤y≤M,A[x][y]=1dist(A[i][j],A[x][y])
输入格式
第一行两个整数 N,M。

接下来一个 N 行 M 列的 01 矩阵,数字之间没有空格。

输出格式
一个 N 行 M 列的矩阵 B,相邻两个整数之间用一个空格隔开。

数据范围
1≤N,M≤1000
输入样例:
3 4
0001
0011
0110
输出样例:
3 2 1 0
2 1 0 0
1 0 0 1
难度:简单
时/空限制:1s / 64MB
总通过数:3432
总尝试数:5652
来源:《算法竞赛进阶指南》, 小马智行面试题
算法标签

//朴素想法:有一个起点的时候。。。 有多个起点:把每个1分别放在队首,遍历一遍,保留最小值,O(n^2),铁T
//图论中求一个点到其他最近的点的最短距离,与多源最短路(任意两点最短距离)不一样,可以转换为单源最短路

//多源  1.两端性 2.单调性  e.g x x x x x ... x+1 x+1 x+1 ...

//本题输入的时候是一串连在一起的数字,中间没有空格相隔,所以要用字符串读,不能用int
//1e3 * 1e3 的 char 类型输入 -> 1e6的字节 只占1MB空间,不算大
#include <bits/stdc++.h>
#define end '\n'
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 1010, M = N * N;

int n, m;
char g[N][N];
PII q[M];
int dist[N][N];

void bfs()
{   
    memset(dist, -1, sizeof(dist));

    int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};

    int hh = 0, tt = -1; // wa!!!

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            if (g[i][j] == '1')
            {
                dist[i][j] = 0;
                q[ ++ tt] = {i, j};
            }

    while (hh <= tt)
    {
        PII t = q[hh ++ ];

        for (int i = 0; i < 4; i ++ )
        {
            int a = t.x + dx[i], b = t.y + dy[i];

            if (a <= 0 || a > n || b <= 0 || b > m) continue;
            if (dist[a][b] != -1) continue;

            dist[a][b] = dist[t.x][t.y] + 1;
            q[ ++ tt] = {a, b};
        }
    }
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) scanf("%s", g[i] + 1);

    bfs();

    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= m; j ++ )
        {
            printf("%d ", dist[i][j]); // %d而非%c wa
        }
        puts("");
    }
    return 0;
}



  1. 魔板
    题目
    提交记录
    讨论
    题解
    视频讲解

Rubik 先生在发明了风靡全球的魔方之后,又发明了它的二维版本——魔板。

这是一张有 8 个大小相同的格子的魔板:

1 2 3 4
8 7 6 5
我们知道魔板的每一个方格都有一种颜色。

这 8 种颜色用前 8 个正整数来表示。

可以用颜色的序列来表示一种魔板状态,规定从魔板的左上角开始,沿顺时针方向依次取出整数,构成一个颜色序列。

对于上图的魔板状态,我们用序列 (1,2,3,4,5,6,7,8) 来表示,这是基本状态。

这里提供三种基本操作,分别用大写字母 A,B,C 来表示(可以通过这些操作改变魔板的状态):

A:交换上下两行;
B:将最右边的一列插入到最左边;
C:魔板中央对的4个数作顺时针旋转。

下面是对基本状态进行操作的示范:

A:

8 7 6 5
1 2 3 4
B:

4 1 2 3
5 8 7 6
C:

1 7 2 4
8 6 3 5
对于每种可能的状态,这三种基本操作都可以使用。

你要编程计算用最少的基本操作完成基本状态到特殊状态的转换,输出基本操作序列。

注意:数据保证一定有解。

输入格式
输入仅一行,包括 8 个整数,用空格分开,表示目标状态。

输出格式
输出文件的第一行包括一个整数,表示最短操作序列的长度。

如果操作序列的长度大于0,则在第二行输出字典序最小的操作序列。

数据范围
输入数据中的所有数字均为 1 到 8 之间的整数。

输入样例:
2 6 8 4 5 7 3 1
输出样例:
7
BCABCCB
难度:简单
时/空限制:1s / 64MB
总通过数:1970
总尝试数:3199
来源:《信息学奥赛一本通》 , usaco training 3.2
算法标签

//最小步数bfs模型(八数码)里,经常用哈希法存状态,而c++里可以用map或umap做哈希
#include <bits/stdc++.h>

using namespace std;

char g[2][4]; //不要int wa,因为是将它与字符串进行操作
unordered_map<string, int> dist;
unordered_map<string, pair<char, string>> pre;
queue<string> q;

void build(string state)
{
    for (int i = 0; i < 4; i ++ ) g[0][i] = state[i];
    for (int j = 3, i = 4; j >= 0; j -- , i ++ ) g[1][j] = state[i];
}

string get()
{
    string ans;
    for (int i = 0; i < 4; i ++ ) ans += g[0][i];
    for (int i = 3; i >= 0; i-- ) ans += g[1][i];
    return ans;
}

string move0(string state)
{
    build(state);
    for (int i = 0; i < 4; i ++ ) swap(g[0][i], g[1][i]);
    return get();
}

string move1(string state)
{
    build(state);
    int v0 = g[0][3], v1 = g[1][3];
    for (int i = 0; i < 2; i ++ )
        for (int j = 3; j >= 0; j -- )
            g[i][j] = g[i][j - 1];

    g[0][0] = v0, g[1][0] = v1;
    return get();
}

string move2(string state)
{
    build(state);
    int v = g[0][1];
    g[0][1] = g[1][1];
    g[1][1] = g[1][2];
    g[1][2] = g[0][2];
    g[0][2] = v;
    return get();
}

int bfs(string start, string end)
{
    if (start == end) return 0;

    dist[start] = 0;
    q.push(start);

    while (q.size())
    {
        auto t = q.front();
        q.pop();

        string m[3];
        m[0] = move0(t);
        m[1] = move1(t);
        m[2] = move2(t);

        for (int i = 0; i < 3; i ++ )
        {
            if (!dist[m[i]])
            {
                dist[m[i]] = dist[t] + 1;
                pre[m[i]] = {'A' + i, t};
                if (m[i] == end) return dist[end];
                q.push(m[i]);
            }
        }
    }

    return -1;
}

int main()
{
    int x;
    string start, end;
    for (int i = 0; i < 8; i ++ )
    {
        int x;
        cin >> x;
        end += char(x + '0');
    }
    for (int i = 0; i < 8; i ++ ) start += char(i + '1');

    int step = bfs(start, end);

    cout << step << endl;

    string res;

    while (end != start)
    {
        res += pre[end].first;
        end = pre[end].second;
    }

    reverse(res.begin(), res.end());
    if (step > 0) cout << res << endl;

    return 0;
}


  1. 电路维修
    题目
    提交记录
    讨论
    题解
    视频讲解

达达是来自异世界的魔女,她在漫无目的地四处漂流的时候,遇到了善良的少女翰翰,从而被收留在地球上。

翰翰的家里有一辆飞行车。

有一天飞行车的电路板突然出现了故障,导致无法启动。

电路板的整体结构是一个 R 行 C 列的网格(R,C≤500),如下图所示。

电路.png

每个格点都是电线的接点,每个格子都包含一个电子元件。

电子元件的主要部分是一个可旋转的、连接一条对角线上的两个接点的短电缆。

在旋转之后,它就可以连接另一条对角线的两个接点。

电路板左上角的接点接入直流电源,右下角的接点接入飞行车的发动装置。

达达发现因为某些元件的方向不小心发生了改变,电路板可能处于断路的状态。

她准备通过计算,旋转最少数量的元件,使电源与发动装置通过若干条短缆相连。

不过,电路的规模实在是太大了,达达并不擅长编程,希望你能够帮她解决这个问题。

注意:只能走斜向的线段,水平和竖直线段不能走。

输入格式
输入文件包含多组测试数据。

第一行包含一个整数 T,表示测试数据的数目。

对于每组测试数据,第一行包含正整数 R 和 C,表示电路板的行数和列数。

之后 R 行,每行 C 个字符,字符是"/“和”"中的一个,表示标准件的方向。

输出格式
对于每组测试数据,在单独的一行输出一个正整数,表示所需的缩小旋转次数。

如果无论怎样都不能使得电源和发动机之间连通,输出 NO SOLUTION。

数据范围
1≤R,C≤500,
1≤T≤5
输入样例:
1
3 5
\/\
\///
/\\
输出样例:
1
样例解释
样例的输入对应于题目描述中的情况。

只需要按照下面的方式旋转标准件,就可以使得电源和发动机之间连通。

电路2.png

在这里插入图片描述

//相当于给了一个无向图,这个无向图中只有两种边,第一种边边权是0,第二种边权是1(前面是所有边边权都是1,可以直接BFS求它最短路),且仅包含01边权
//问在这样的图上,从起点到终点的最短路径是多少 ,可以用dijkstra做(dijk要求其实是在非负权值图上做)

//因为沿斜线走的,一开始横纵坐标之和是偶数,所以之后也都是偶数,所以是之和是奇数的点一定到不了

//把边权为0的拓展到队首,边权为1的拓展到队尾,仍满足两段性和单调性 x x .. x || x x .. x || x+1 x+1 .. x+1 || x+2 x+2 .. x+2 

//每个点可能会被更新入队多次(di=dj, di+1>dj+0),和一般bfs(d<=d_  -> d+1 <= d_+1,前面更新的肯定小于等于后面更新的,所以每个点只会入队一次)不一样,本质上dijk

//每个点第一次被拓展到时不一定是最优解,只有这个点第一次从堆中出来的时候,也就是当前点是当前堆中最小值时,它的值才一定是最优解,这就是一个简化版的dijk
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;

const int N = 550, M = N * N;

int n, m;
char g[N][N];
int dist[N][N];
bool st[N][N];

int bfs()
{
    deque<PII> q;
    memset(st, 0, sizeof(st));
    memset(dist, 0x3f, sizeof(dist)); //dist初始化不再是-1,因为需要更新最小值

    char cs[5] = "\\/\\/";  // 转义字符  4 -> 5 注意顺序是顺时针(和下面dx, ix都是对应的)数过来那个点周围的四个方格的
    int dx[] = {-1, -1, 1, 1}, dy[] = {-1, 1, 1, -1};
    int ix[] = {-1, -1, 0, 0}, iy[] = {-1, 0, 0, -1};

    q.push_back({0, 0});
    dist[0][0] = 0;

    while (q.size())
    {
        auto t = q.front();
        q.pop_front();

        int x = t.first, y = t.second;

        if (x == n && y == m) return dist[n][m];

        if (st[x][y]) continue;
        st[x][y] = 1;

        for (int i = 0; i < 4; i ++ )
        {
            int a = x + dx[i], b = y + dy[i];
            if (a < 0 || a > n || b < 0 || b > m) continue; //输入进的是方格个数,而格点是[0,n]
            int ga = x + ix[i], gb = y + iy[i];
            int w = (g[ga][gb] != cs[i]);
            int d = dist[x][y] + w;
            if (d <= dist[a][b])
            {
                dist[a][b] = d;
                if (!w) q.push_front({a, b});
                else q.push_back({a, b});
            }
        }
    }

    return -1;
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d %d", &n, &m);

        for (int i = 0; i < n; i ++ ) scanf("%s", g[i]); //和下一条的先后顺序,即使一开始就判断出答案,也要先输入

        if (n + m & 1) puts("NO SOLUTION");
        else printf("%d\n", bfs());
    }

    return 0;
}



  1. 字串变换
    题目
    提交记录
    讨论
    题解
    视频讲解

已知有两个字串 A, B 及一组字串变换的规则(至多 6 个规则):

A1→B1
A2→B2

规则的含义为:在 A 中的子串 A1 可以变换为 B1、A2 可以变换为 B2…。

例如:A=abcd B=xyz

变换规则为:

abc → xu ud → y y → yz

则此时,A 可以经过一系列的变换变为 B,其变换的过程为:

abcd → xud → xy → xyz

共进行了三次变换,使得 A 变换为 B。

输入格式
输入格式如下:

A B
A1 B1
A2 B2
… …

第一行是两个给定的字符串 A 和 B。

接下来若干行,每行描述一组字串变换的规则。

所有字符串长度的上限为 20。

输出格式
若在 10 步(包含 10 步)以内能将 A 变换为 B ,则输出最少的变换步数;否则输出 NO ANSWER!。

输入样例:
abcd xyz
abc xu
ud y
y yz
输出样例:
3

//即使只有6种规则,一次op分支是很多的,复杂度很高,所以要a和b相互找对方
//复杂度 e.g k^10 -> 2*k^5 指数级变化 搜索需要的状态量面积变为原先的比如2/k^5

//搜索宽度太宽时(单向超时时)用双向

//双向bfs搜时, 两边每次扩展的时候必须把一整层扩展出来,否则wa,不能每次只扩展一个点
#include <bits/stdc++.h>
using namespace std;

const int N = 10;

string A, B;
int n;
string a[N], b[N];

int extend(queue<string>& q, unordered_map<string, int>& da, unordered_map<string, int>& db, string a[N], string b[N])
{
    int d = da[q.front()];
	//一次拓展一层

    while (q.size() && da[q.front()] == d)
    {
        auto t = q.front();
        q.pop();

        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < (int)t.size(); j ++ )
            {
                if (t.substr(j, a[i].size()) == a[i])
                {
                    string r = t.substr(0, j) + b[i] + t.substr(j + a[i].size());
                    //substr只传一个参数表示从这个位置到末尾的子串

                    if (db.count(r)) return da[t] + 1 + db[r];
                    if (da.count(r)) continue;

                    da[r] = da[t] + 1;
                    q.push(r);
                }
            }
    }
    return 11;
}

int bfs()
{
    queue<string> qa, qb;
    unordered_map<string, int> da, db;

    da[A] = 0, db[B] = 0;
    qa.push(A), qb.push(B);

    int step = 0;

    while (qa.size() && qb.size())
    {
        int t;
        //用小的那个拓展
        if (qa.size() <= qb.size()) t = extend(qa, da, db, a, b);
        else t = extend(qb, db, da, b, a);

        if (t <= 10) return t;
        if ( ++ step == 11) return -1;
    }

    return -1;
}

int main()
{
    cin >> A >> B;
    while (cin >> a[n] >> b[n]) n ++ ;

    int t = bfs();

    if (t == -1) puts("NO ANSWER!");
    else printf("%d\n", t);

    return 0;
}



//2.2.1 DFS中的连通性和搜索顺序

//有些问题既可以深搜做也可以宽搜 基于连通性的模型的问题  比如flood-fill模型,或有关于图和树的遍历
///它们都有个特点,判断从一个点能否走到某个点,是在棋盘内部做的,包括树的遍历也是,,是一个局部信息

//dfs的另一类题一般都是看成一个整体,问能否从这一个整体变成另一个整体,是把一个整体看出一个点,

  1. 迷宫
    题目
    提交记录
    讨论
    题解
    视频讲解

一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由 n∗n 的格点组成,每个格点只有2种状态,.和#,前者表示可以通行后者表示不能通行。

同时当Extense处在某个格点时,他只能移动到东南西北(或者说上下左右)四个方向之一的相邻格点上,Extense想要从点A走到点B,问在不走出迷宫的情况下能不能办到。

如果起点或者终点有一个不能通行(为#),则看成无法办到。

注意:A、B不一定是两个不同的点。

输入格式
第1行是测试数据的组数 k,后面跟着 k 组输入。

每组测试数据的第1行是一个正整数 n,表示迷宫的规模是 n∗n 的。

接下来是一个 n∗n 的矩阵,矩阵中的元素为.或者#。

再接下来一行是 4 个整数 ha,la,hb,lb,描述 A 处在第 ha 行, 第 la 列,B 处在第 hb 行, 第 lb 列。

注意到 ha,la,hb,lb 全部是从 0 开始计数的。

输出格式
k行,每行输出对应一个输入。

能办到则输出“YES”,否则输出“NO”。

数据范围
1≤n≤100
输入样例:
2
3
.##
…#
#…
0 0 2 2
5

###.#
…#…
###…
…#.
0 0 4 0
输出样例:
YES
NO

//深搜解迷宫问题局限性:深搜只能求出能否连通,不能保证第一次到时最短

//每个点最多只被遍历到一次,所以复杂度是线性的

//这里的dfs不需要回溯  因为这里的状态是格子,每个格子不需要被重复搜索,所以不需要恢复原状

//求连通性问题不需要回溯? 因为遍历后不需要再从那点开始遍历;以单个格子为状态
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;

const int maxn = 110;

char g[maxn][maxn];
bool st[maxn][maxn];
int n;
int ax, ay, bx, by;

int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};

bool dfs(int x, int y)
{
    if (g[x][y] == '#') return 0;
    if (x == bx && y == by) return 1;

    for (int i = 0; i < 4; ++ i)
    {
        int a = x + dx[i], b = y + dy[i];

        if (a < 0 || a >= n || b < 0 || b >= n) continue;
        if (st[a][b]) continue;
        
        st[a][b] = 1;
        if (dfs(a, b)) return 1;
    }

    return 0;
}

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        cin >> n;
        for (int i = 0; i < n; ++ i) cin >> g[i];

        memset(st, 0, sizeof st);

        cin >> ax >> ay >> bx >> by;

        if (dfs(ax, ay)) puts("YES");
        else puts("NO");
    }
    return 0;
}

//深搜的两大类模型

//从人体内部的一个点到另一个点(棋盘的一个点到棋盘的另一个点),为了保证每个点只被搜一次,不能恢复现场

//但如果把整个棋盘看成一个状态,当搜到一个分支后,一定要把棋盘恢复原状才可以搜下一个分支 回溯
  1. 红与黑
    题目
    提交记录
    讨论
    题解
    视频讲解

有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。

你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动。

请写一个程序,计算你总共能够到达多少块黑色的瓷砖。

输入格式
输入包括多个数据集合。

每个数据集合的第一行是两个整数 W 和 H,分别表示 x 方向和 y 方向瓷砖的数量。

在接下来的 H 行中,每行包括 W 个字符。每个字符表示一块瓷砖的颜色,规则如下

1)‘.’:黑色的瓷砖;
2)‘#’:红色的瓷砖;
3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。

当在一行中读入的是两个零时,表示输入结束。

输出格式
对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。

数据范围
1≤W,H≤20
输入样例:
6 9
…#.
…#





#@…#
.#…#.
0 0
输出样例:
45
难度:简单
时/空限制:1s / 64MB
总通过数:9374
总尝试数:16300
来源:《信息学奥赛一本通》
算法标签

//flood-fill 用bfs或dfs均可,只是dfs有爆栈的风险  1M -> 256M

#include <bits/stdc++.h>
#define endl '\n'
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;

const int maxn = 25;

int n, m;
char g[maxn][maxn];
int res = 0;

int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};

void bfs(int x, int y)
{
    queue<PII> q;
    q.push({x, y});

    while (q.size())
    {
        auto t = q.front();
        q.pop();

        res ++ ;

        for (int i = 0; i < 4; ++ i)
        {
            int a = t.x + dx[i], b = t.y + dy[i];

            if (a < 0 || a >= n || b < 0 || b >= m) continue;
            if (g[a][b] != '.') continue;

            g[a][b] = '#';
            q.push({a, b});
        }
    }
}

int main()
{
    while (cin >> m >> n, n || m)
    {
        for (int i = 0; i < n; ++ i) cin >> g[i];

        res = 0;

        int x, y;
        for (int i = 0; i < n; ++ i)
            for (int j = 0; j < m; ++ j)
            {
                if (g[i][j] == '@')
                    x = i, y = j;
            }

        bfs(x, y);

        cout << res << endl;
    }
    return 0;
}

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const int maxn = 25;

int n, m;
char g[maxn][maxn];

int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};

int dfs(int x, int y)
{
    int res = 1;
    g[x][y] = '#';

    for (int i = 0; i < 4; i ++ )
    {
        int a = x + dx[i], b = y + dy[i];
        
        if (a < 0 || a >= n || b < 0 || b >= m) continue;
        if (g[a][b] != '.') continue;

        res += dfs(a, b);
    }

    return res;
}

int main()
{
    while (cin >> m >> n, n || m)
    {
        for (int i = 0; i < n; ++ i) cin >> g[i];

        int x, y;
        for (int i = 0; i < n; ++ i)
            for (int j = 0; j < m; ++ j)
                if (g[i][j] == '@')
                    x = i, y = j;

        cout << dfs(x, y) << endl;
    }
    return 0;
}

在这里插入图片描述

//后面的外部搜索

//求最值,求数量   和dp问题一样,但爆搜比较简单,dp一个集合
  1. 马走日
    题目
    提交记录
    讨论
    题解
    视频讲解

马在中国象棋以日字形规则移动。

请编写一段程序,给定 n∗m 大小的棋盘,以及马的初始位置 (x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点。

输入格式
第一行为整数 T,表示测试数据组数。

每一组测试数据包含一行,为四个整数,分别为棋盘的大小以及初始位置坐标 n,m,x,y。

输出格式
每组测试数据包含一行,为一个整数,表示马能遍历棋盘的途径总数,若无法遍历棋盘上的所有点则输出 0。

数据范围
1≤T≤9,
1≤m,n≤9,
0≤x≤n−1,
0≤y≤m−1
输入样例:
1
5 4 0 0
输出样例:
32

//搜索树

//这题是,搜索树中,每个状态都是一个节点,每变化一步,都变一下变一下,它每个儿子都是把父节点复制过去变一下

//恢复现场 下去是什么样回来就是什么样  所以这里不需要st初始化,因为回来的时候和下去时候一样全是0

//一般不会去问dfs的时间复杂度,一般认为是指数的,,除非是内部搜索,每个点只搜一次,所以是线性的

//这是个外部搜索 所以需要恢复现场(相当于是否需要回溯?

//将st变为0或1,放外面而不放for内部的话,可以减少运算,快一点

//搜索结束就是把点搜完   所以再传一个参数,当前在搜第几个点
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const int N = 15;

int n, m;
bool st[N][N];
int ans;

int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2}, dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};

void dfs(int x, int y, int cnt)
{
    if (cnt == n * m)
    {
        ans ++ ;
        return ;
    }

    st[x][y] = 1;

    for (int i = 0; i < 8; i ++ )
    {
        int a = x + dx[i], b = y + dy[i];

        if (a < 0 || a >= n || b < 0 || b >= m) continue;
        if (st[a][b]) continue;

        dfs(a, b, cnt + 1);
    }

    st[x][y] = 0;
}

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        int x, y;
        cin >> n >> m >> x >> y;

        ans = 0;
        dfs(x, y, 1);

        cout << ans << endl;
    }
    return 0;
}

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const int N = 15;

int n, m;
bool st[N][N];
int ans, cnt;

int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2}, dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};

void dfs(int x, int y)
{
    if (cnt == n * m)
    {
        ans ++ ;
        return ;
    }

    st[x][y] = 1;
    cnt ++ ;

    for (int i = 0; i < 8; i ++ )
    {
        int a = x + dx[i], b = y + dy[i];

        if (a < 0 || a >= n || b < 0 || b >= m) continue;
        if (st[a][b]) continue;

        dfs(a, b);
    }

    st[x][y] = 0;
    cnt -- ;
}

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        int x, y;

        cin >> n >> m >> x >> y;

        ans = 0;
        cnt = 1;

        dfs(x, y);

        cout << ans << endl;
    }
    return 0;
}

#include <bits/stdc++.h>
using namespace std;
const int N = 15;

int n, m, ans;
bool st[N][N];

int dx[] = {-2, -1, 1, 2, 2, 1, -1, -2}, dy[] = {1, 2, 2, 1, -1, -2, -2, -1};

void dfs(int x, int y, int cnt)
{
    // st[x][y] = 1; 都可以
    if (cnt == n * m)
    {
        ans ++ ;
        // return ;  这里不能return不然的话就没办法恢复状态了
    }

    st[x][y] = 1;

    for (int i = 0; i < 8; i ++ )
    {
        int a = x + dx[i], b = y + dy[i];
        if (a < 0 || a >= n || b < 0 || b >= m) continue;
        if (st[a][b]) continue;
        dfs(a, b, cnt + 1);
    }

    st[x][y] = 0;
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d%d", &n, &m);
        int x, y;
        scanf("%d%d", &x, &y);
        ans = 0;
        dfs(x, y, 1);
        cout << ans << endl;
    }

    return 0;
}
  1. 单词接龙
    题目
    提交记录
    讨论
    题解
    视频讲解

单词接龙是一个与我们经常玩的成语接龙相类似的游戏。

现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”,每个单词最多被使用两次。

在两个单词相连时,其重合部分合为一部分,例如 beast 和 astonish ,如果接成一条龙则变为 beastonish。

我们可以任意选择重合部分的长度,但其长度必须大于等于1,且严格小于两个串的长度,例如 at 和 atide 间不能相连。

输入格式
输入的第一行为一个单独的整数 n 表示单词数,以下 n 行每行有一个单词(只含有大写或小写字母,长度不超过20),输入的最后一行为一个单个字符,表示“龙”开头的字母。

你可以假定以此字母开头的“龙”一定存在。

输出格式
只需输出以此字母开头的最长的“龙”的长度。

数据范围
n≤20
输入样例:
5
at
touch
cheat
choose
tact
a
输出样例:
23
提示
连成的“龙”为 atoucheatactactouchoose。

//表述“每个单词最多被使用两次“和”每个单词最多在龙中出现两次“的区别,包含,一个单词在另一个单词中出现,kmp算法
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const int N = 25;

string word[N];
int g[N][N];
int used[N];
int ans, n;

void dfs(string dragon, int last)
{
    ans = max(ans, (int)dragon.size()); //max函数里两个参数必须同一类型

    used[last] ++ ; //虽说last,其实是当前字符串的下标

    for (int i = 0; i < n; i ++ )
        if (g[last][i] && used[i] < 2)
            //与重合长度有关来推公式
            dfs(dragon + word[i].substr(g[last][i]), i); //substr只传一个参数表示从这个位置开始到末尾的子串,用来求后缀

    used[last] -- ;
}

int main()
{
    cin >> n;
    char start;
    for (int i = 0; i < n; i ++ ) cin >> word[i];
    cin >> start;

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
        {
            string a = word[i], b = word[j];
            for (int k = 1; k < min(a.size(), b.size()); k ++ ) //因为重合的越少dragon越长,所以k从1个开始
                if (a.substr(a.size() - k, k) == b.substr(0, k)) //后缀等于前缀
                {
                    g[i][j] = k;
                    break; //找到最小的就好
                }
        }

    for (int i = 0; i < n; i ++ )
        if (word[i][0] == start)
            dfs(word[i], i);

    cout << ans << endl;
    return 0;
}




  1. 分成互质组
    题目
    提交记录
    讨论
    题解
    视频讲解

给定 n 个正整数,将它们分组,使得每组中任意两个数互质。

至少要分成多少个组?

输入格式
第一行是一个正整数 n。

第二行是 n 个不大于10000的正整数。

输出格式
一个正整数,即最少需要的组数。

数据范围
1≤n≤10
输入样例:
6
14 20 33 117 143 175
输出样例:
3

//可以转化成图论问题,如果两1118. 分成互质组

//如果是组合问题就不要做成排列问题了
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;

int n, len;
int ans;
int a[10];
vector<int>g[10];

int inline gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

bool inline check(int v, int u)
{
    for (int i = 0; i < g[v].size(); i ++ )
        if (gcd(g[v][i], u) > 1) return 0;
    return 1;
}

void dfs(int u)
{
    if (u == n)
    {
        ans = min(ans, len);
        return ;
    }

    for (int i = 0; i < len; i ++ )
        if (check(i, a[u]))
        {
            g[i].push_back(a[u]);

            dfs(u + 1);

            g[i].pop_back();
        }

    g[len ++ ].push_back(a[u]);

    dfs(u + 1);

    g[ -- len].pop_back();
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", a + i);
    ans = n;

    dfs(0);

    printf("%d\n", ans);
}

#include <bits/stdc++.h>
using namespace std;
const int N = 15;

int a[N];
int n, ans = N;
bool st[N];
int group[N][N];

int inline gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

bool inline check(int g, int gc, int i)
{
    for (int j = 0; j < gc; j ++ )
    {
        if (gcd(a[group[g][j]], a[i]) != 1) return false;
    }
    return true;
}

void dfs(int g, int gc, int tc, int start)
{
    if (g >= ans) return ;
    if (tc == n)
    {
        ans = min(ans, g);
        return ;
    }

    bool flag = 1;

    for (int i = start; i < n; i ++ )
    {
        if (!st[i] && check(g, gc, i))
        {
            st[i] = 1;
            group[g][gc] = i;

            dfs(g, gc + 1, tc + 1, i + 1);

            st[i] = 0;
            flag = 0;
        }
    }

    if (flag) dfs(g + 1, 0, tc, 0);
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ ) scanf("%d", a + i);

    dfs(1, 0, 0, 0);

    cout << ans << endl;

    return 0;
}





//2.2.2 DFS之剪枝
//1.优化搜索顺序
//2.排除等效冗余
//3.可行性剪枝
//4.最优性剪枝
//5.记忆化搜索(DP)


  1. 小猫爬山
    题目
    提交记录
    讨论
    题解
    视频讲解

翰翰和达达饲养了 N 只小猫,这天,小猫们要去爬山。

经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。

翰翰和达达只好花钱让它们坐索道下山。

索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C1、C2……CN。

当然,每辆缆车上的小猫的重量之和不能超过 W。

每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?

输入格式
第 1 行:包含两个用空格隔开的整数,N 和 W。

第 2…N+1 行:每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。

输出格式
输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。

数据范围
1≤N≤18,
1≤Ci≤W≤108
输入样例:
5 1996
1
2
1994
12
29
输出样例:
2
难度:简单
时/空限制:1s / 64MB
总通过数:5596
总尝试数:12283
来源:《算法竞赛进阶指南》
算法标签

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 20;

ll n, w, ans = N;
vector<ll>g;
ll cat[N];

void dfs(int u)
{
    if (g.size() >= ans) return ;
    if (u == n)
    {
        ans = min(ans, (ll)g.size());
        return ;
    }

    for (int i = 0; i < g.size(); i ++ )
    {
        if (g[i] + cat[u] <= w)
        {
            g[i] += cat[u];
            dfs(u + 1);
            g[i] -= cat[u];
        }
    }

    g.push_back(cat[u]);
    dfs(u + 1);
    g.pop_back();
}

int main()
{
    cin >> n >> w;
    for (int i = 0; i < n; i ++ ) scanf("%d", cat + i);
    dfs(0);
    cout << ans << endl;

    return 0;
}


在这里插入图片描述

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
const int N = 20;

int n, w;
int cat[N];
int ans = N;
int sum[N];

void dfs(int u, int k)
{
    if (k >= ans) return ; //最优性剪枝
    if (u == n)
    {
        ans = k;
        return ;
    }

    for (int i = 0; i < k; i ++ )
    {
        if (sum[i] + cat[u] <= w) //可行性剪枝
        {
            sum[i] += cat[u];
            dfs(u + 1, k);
            sum[i] -= cat[u];
        }
    }

    sum[k] = cat[u]; //[0, k - 1],所以sum[k]是新开的
    dfs(u + 1, k + 1);
    sum[k] -= cat[u];
}

int main()
{
    cin >> n >> w;
    for (int i = 0; i < n; i ++ ) scanf("%d", cat + i);
    //优化搜索顺序
    sort(cat, cat + n);
    reverse(cat, cat + n);
    dfs(0, 0);
    cout << ans << endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值