【马蹄集】—— 搜索专题

搜索专题






MT2235 圣诞节送花

难度:钻石    时间限制:1秒    占用内存:128M
题目描述

今天是圣诞节,小码哥拿着 5 束花就想要去送给他的朋友们表示祝福,但后来他才发现远远不够,于是决定在花店买一些花。
当小码哥遇到一家花店时,他会买下和自己手中数量一样的花,当遇到朋友时,则会送出 2 束。
现在知道了他外出遇到了 m m m 个花店和 n n n 个朋友,并且遇完所有花店和朋友后,花也正好送完,你能根据 m m m n n n 计算出有多少种合适的次序吗(合适的次序表示小码哥遇到朋友时总是有花可以送,且遇到了 m m m 家花店和 n n n 个朋友,且回到家后手里不剩花)?

格式

输入格式:第一行两个数 m m m n n n ,分别表示遇到花店、朋友的数量( 1 ≤ n , m ≤ 15 1\le n,m\le15 1n,m15)。
输出格式:输出一个整数表示合适次序的数量。

样例 1

输入:
4 9

输出:
8


相关知识点:深度优先搜索


题解


这是一道非常基础且简单的搜索题目,其中涉及到 3 个变量:花(flowers)、花店(stores)、朋友(friends)。初始时,你有 5 枝花。接下来每当遇到一家花店时,你会买下和自己手中数量一样的花;当遇到朋友时,则会送出 2 束。问,最终你手上刚好没花的次序有多少种?

假设搜索函数为 dfs(flowers, stores, friends),则本题相当于要求 dfs(flowers, stores, friends) 从状态 (5, m, n) 转换到状态 (0, 0, 0) 的方案数( m m m n n n 分别表示输入的花店和朋友数)。而从题目描述可知:

  1. 遇到一家花店时,你会买下和自己手中数量一样的花:
    d f s ( f l o w e r s , s t o r e s , f r i e n d s ) → d f s ( f l o w e r s ∗ 2 , s t o r e s − 1 , f r i e n d s ) dfs(flowers, stores, friends) → dfs(flowers*2, stores-1, friends) dfs(flowers,stores,friends)dfs(flowers2,stores1,friends)

  2. 遇到朋友时,会送出 2 束花:
    d f s ( f l o w e r s , s t o r e s , f r i e n d s ) → d f s ( f l o w e r s − 2 , s t o r e s , f r i e n d s − 1 ) dfs(flowers, stores, friends) → dfs(flowers-2, stores, friends-1) dfs(flowers,stores,friends)dfs(flowers2,stores,friends1)

这便是所有的状态转移过程。一旦某一次状态转移使得:

  1. 手上花束数量小于 0(flowers < 0);
  2. 花店数量小于 0(stores < 0);
  3. 朋友数量小于 0(friends < 0)。

都表示这是一种不合理的次序,故返回 0 即可。

而当某一次得到的状态满足 flowers = stores = friends = 0 时,就表示找到了一种合理的次序,故需返回 1。

基于此,可得到求解本题的完整代码:

/*
    MT2235 圣诞节送花 
    深度优先搜索 
*/
#include<bits/stdc++.h> 
using namespace std; 

int dfs(int flowers, int stores, int friends)
{
    // 手中的花全部送完(同时逛完所有店铺,见过所有朋友) 
    if(flowers == 0 && stores == 0 && friends == 0) return 1;
    // 任意值为负时都表示一种不可能成立的情况
    if(flowers < 0 || stores < 0 || friends < 0)  return 0;
    // 进入一家花店买花 或 送 2 束花给朋友 
    return dfs(flowers*2, stores - 1, friends) + dfs(flowers - 2, stores, friends - 1);
}

int main()
{
    // 获取输入
    int m, n; cin>>m>>n;
    // 构建全部的 47 数
    cout<<dfs(5, m, n)<<endl;
    return 0;
} 

但是这个代码交上去居然不能拿满分?甚至连测试数据都过不了。。。

下面是能 AC 的满分代码:

/*
    MT2235 圣诞节送花(ACC) 
    深度优先搜索 
*/
#include<bits/stdc++.h> 
using namespace std; 

int dfs(int flowers, int stores, int friends)
{
	// 手中的花全部送完(同时逛完所有店铺,见过所有朋友) 
	if(flowers == 0 && stores == 0 && friends == 0) return 1;
	// 任意值为负时都表示一种不可能成立的情况
	if(flowers <= 0 || stores < 0 || friends < 0)  return 0;
	// 进入一家花店买花 或 送 2 束花给朋友 
	return dfs(flowers*2, stores - 1, friends) + dfs(flowers - 2, stores, friends - 1);
}

int main()
{
    // 获取输入
    int m, n; cin>>m>>n;
    // 构建全部的47数
	cout<<dfs(5, m, n)<<endl;
    return 0;
} 

注意到这个代码仅仅是在返回 0 的条件中,将 flowers < 0 替换为 flowers <=0 。而这样的逻辑显然是不正确的,因为存在一种情况:遇到一个朋友,在送出花之后,手上已经没有花(即 flowers 为 0),于是接下来遇到任意数量的花店都不会再增加 flowers 的值。在这样的前提下,只要这之后不再遇到朋友,那么手上花的数量当然也不会为负。即,这种情况下的序列应该也是合法的才对。

不过,这也只是我的拙见。

最终官方认定的才是唯一正解。



MT2236 枚举方案

难度:黄金    时间限制:1秒    占用内存:128M
题目描述

从1到 n n n n n n 个整数中随机选出 m m m 个,输出所有可能的选择方案。

现在问最多能生成多少个不同的数。

格式

输入格式:两个整数 n ,   m n,\ m n, m。。
输出格式:输出每一种可能的方案,一行输出一种,按字典序输出。

样例 1

输入:
5 3

输出:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5

备注

其中: 1 ≤ m ≤ n ≤ 20 1\le m\le n\le20 1mn20


相关知识点:深度优先搜索


题解


输出具体的排列方案实际上是一个广义的深度优先搜索过程,即每种状态都只会被访问一次。对于这个问题,我们的搜索函数往往需要记录两个参数(用以标识对某种状态的选择),一是当前选择了哪个数(这个状态用于向后继续搜索),二是当前选了多少数(这个状态也指明了搜索深度)。然后在搜索函数内部,通过一个循环结构实现从当前状态向后进行选数(这样的顺序一方面保证了每种状态仅被访问一次,另一方面则保证了选数的过程是一个按字典序进行的过程)。在搜索函数的入口,一旦“当前选数个数”等于\ m\ ,就表示找到了一种组合方案,此时便需要进行输出。为了记录这些被选中的数(输出时用),我们还要单独创建一个标记数组,来记录在该轮被选中的数。注意:标记数组中的值在退出一层搜索时(表示这个数被选的情况已经遍历完毕),需要将其标记为假。

下面直接给出基于以上思路得到的完整代码:

/*
    MT2236 枚举方案 
    搜索 
*/
#include<bits/stdc++.h> 
using namespace std; 

const int MAX = 25; 
// 总数、选数、选数标记 
int n, m, vis[MAX]; 

// 从 n 个数中选出 m 个数的方案
void getCombinations(int pos, int num)
{
    // pos:当前取数的位置
    // num:当前取了多少数 
    // 若取数个数已经达到上限,则执行一次输出 
    if(num == m){
        for(int i=1; i<=n; i++)
            if(vis[i]) cout<<i<<" ";
        cout<<endl;
    }
    // 选数 
    for(int i = pos+1; i<=n; i++){
        // 选中当前数 
        vis[i] = 1;
        // 继续向后选 
        getCombinations(i, num+1);
        // 以 pos 为起点,并选择 i 的全部情况都已完成枚举,故将这个数的标记重置 
        vis[i] = 0;
    }
}

int main()
{
    // 获取输入
    cin>>n>>m;
    // 输出全部方案 
    getCombinations(0, 0); 
    return 0;
} 


MT2237 47论

难度:黄金    时间限制:1秒    占用内存:128M
题目描述

小码哥数是只含有 4、7,且 4 和 7 个数相同的数字。
请你寻找大于等于 n ( 1 ≤ n ≤ 10 9 ) n\left(1\le n\le{10}^9\right) n(1n109) 的最小小码哥数。

格式

输入格式:输入包含一个整数 n ( 1 ≤ n ≤ 10 9 ) n\left(1\le n\le{10}^9\right) n(1n109)
输出格式:输出大于等于\ n\ 的最小小码哥数

样例 1

输入:

4500

输出:

4747

备注

其中: n ≤ 10 3 n\le{10}^3 n103
测试数据中生成了:
5→15
5→25→125
共4个数字(不计算重复数字)。


相关知识点:深度优先搜索


题解


对于本题给出的 小码哥数,在规定范围内并不是特别多,因此我们可以提前求出全部小码哥数,然后在给定输入时,从中按序遍历,直到找到第一个不大于该指定数的小码哥数即可。

那要如何求出全部的小码哥数呢?从排列的角度进行枚举,这个过程如下:

  1. 2 位数时:44、47、74、77,其中只有47和74符合要求;
  2. 4 位数时:4444、4447、4474、4477、4744、4747、4774、4777、7444、7447、7474、7477、7744、7747、7774、7777,其中只有4477、4747、4774、7447、7474、7744符合要求
    ……

我们发现,确定小码哥数需要记录 2 个关键信息:

  1. 当前有多少个 4(num4);
  2. 当前有多少个 7(num7)。

因此,我们可通过在深度优先搜索中加入这 2 个参数来完成枚举。在搜索函数内部,一旦 num4 等于 num7,就表示找到一个小码哥数,那就直接将当前数记录下来(可用一个线性数组或带顺序的容器来存储)。值得注意的是,具体的搜索过程需要一个记录“当前位置”(即当前枚举到哪个数)的变量,因此在具体设计搜索函数时,还需要再额外添加一个参数 num 以记录当前枚举的数字(它的作用是实现向后查找与枚举)。另外,对本题而言只需搜索 10 位数以内的小码哥数(这之内必定出现本题的解),超过这范围的就不需要了。

下面直接给出基于以上思路得到的求解本题的完整代码:

/*
    MT2237 47论 
    搜索 
*/
#include<bits/stdc++.h> 
using namespace std; 

// 存放47数的集合
set<long long> s;
// 构建全部含4、7且位数相同的数 
void get47Number(long long num, int len, int num4, int num7)
{
    // num:当前数为 num 
    // len:其长度为 len  
    // 4num:其中包含 4 的个数 
    // 7num:其中包含 7 的个数 
    // 若 num 非 0 且其包含的 4 和 7 的数量相等则插入 
    if(num && num4==num7) s.insert(num);
    // 长度超过 10 时就不用继续构建了 
    if(len > 10) return;
    // 深度优先搜索查找更多的47数
    // 注:下面的添加顺序使得我们构建的集合 s 是按增序插入的 
    // 向 num 的末尾添加 4 
    get47Number(num*10+4, len+1, num4+1, num7);
    // 向 num 的末尾添加 7
    get47Number(num*10+7, len+1, num4, num7+1);
}

int main()
{
    // 获取输入
    int n;cin>>n;
    // 构建全部的47数
     get47Number(0, 1, 0, 0);
    // 查找大于指定数的最小47数
    for(long long num: s){
        if(num >= n){
            cout<<num<<endl;
            break;
        }
    }
    return 0;
} 


MT2238 数的增殖

难度:黄金    时间限制:1秒    占用内存:128M
题目描述

给定一个数 n ( n < 1000 ) n (n<1000) n(n<1000),可以按以下方法进行增殖:

  1. 取原数最高位,在左边加上不大于最高位一半的数;
  2. 不做处理。

现在问最多能生成多少个不同的数。

格式

输入格式:第一行输入一个正整数 n n n
输出格式:一个正整数,表示最多能生成的数的个数。

样例 1

输入:
5

输出:
4

备注

其中: n ≤ 10 3 n\le{10}^3 n103
测试数据中生成了:
5→15
5→25→125
共4个数字(不计算重复数字)。


相关知识点:深度优先搜索


题解


这道题按其意思进行模拟即可。定义两个函数:

函数一:取出当前数的最高位(循环取模实现)。

// 获取数的最高位值
int getHighestOrder(int n)
{
    int highestOrder;
    while(n){
        highestOrder = n%10;
        n /= 10;
    }
    return highestOrder;
}

函数二:对最高位的数 n n n 按要求进行处理,即在当前数 n n n 的前面添加一个数 m   ( m ∈ [ 1 ∼ ⌊ n 2 ⌋ ] ) m\ (m\in\left[1\sim\left\lfloor\frac{n}{2}\right\rfloor\right]) m (m[12n])。显然,函数二是一个递归调用的过程。一旦最高位数 n n n 的一半小于1时,整个过程就停止了(注:这种情况下,这个数本身应该要被算进去,因此要返回 1)。

// 获取数的增值 
int getAddedValue(int n)
{
    int addedValue = 1;
    int halfHighestValue = getHighestOrder(n) / 2;
    for(int i=1; i<=halfHighestValue; i++)
        addedValue += getAddedValue(i);
    return addedValue;
}

基于此,可得到求解本题的完整代码:

/*
    MT2238 数的增值 
    搜索 
*/ 
#include<bits/stdc++.h> 
using namespace std;

// 获取数的最高位值
int getHighestOrder(int n)
{
    int highestOrder;
    while(n){
        highestOrder = n%10;
        n /= 10;
    }
    return highestOrder;
}
// 获取数的增值 
int getAddedValue(int n)
{
    int addedValue = 1;
    int halfHighestValue = getHighestOrder(n) / 2;
    for(int i=1; i<=halfHighestValue; i++)
        addedValue += getAddedValue(i);
    return addedValue;
}

int main( )
{
    // 录入数据 
    int n;cin>>n;
    // 输出数的增值
    cout<<getAddedValue(n)<<endl;
    return 0;
}


MT2239 二维矩阵中的最长下降序列

难度:钻石    时间限制:1秒    占用内存:128M
题目描述

给定一个 n × m n\times m n×m 的矩阵,然后可以随机选取一个点作为起始点,每次可以选择该点的上下左右四个相邻点中的一个并且保证相邻点的值小于当前点的值。问最长下降序列的长度是多少?

格式

输入格式:第一行输入两个正整数 n ,   m   ( 1 ≤ n ,   m ≤ 100 ) n,\ m\ \left(1\le n,\ m\le100\right) n, m (1n, m100)
     接下来 n n n 行,每行 m m m 个数字 a [ i ] [ j ]   ( ( 1 ≤ a [ i ] [ j ] ≤ 4 × 10 4 ) ) a\left[i\right]\left[j\right]\ (\left(1\le a\left[i\right]\left[j\right]\le4{\times10}^4\right)) a[i][j] ((1a[i][j]4×104))
输出格式:输出最长下降序列的长度。

样例 1

输入:
5 4
1 2 3 4
16 17 18 19
15 24 25 20
14 23 22 21
13 12 11 10

输出:
16

备注

样例中,选取起始点 25,然后 25→24→23→……→11→10,长度为 16。


相关知识点:深度优先搜索记忆化搜索


题解


题目的意思是在一个 n × m n\times m n×m 的矩阵中找一个位置,使得从该位置出发(只能往上、下、左、右四个方向行走,且下一个位置上的值必须小于当前位置上的值)能得到最长的路径长度。这是一道非常典型的DFS走迷宫问题。最简单粗暴的方式就是遍历整个矩阵中的全部位置,算出从该位置出发得到的最长路径长度。但遗憾的是,在题目给出的最广可达 100 × 100 100\times100 100×100 的数据范围下,这种办法肯定不能拿到满分。

注意到一个事,该办法超时的原因在于,每次基于某个位置进行深度优先搜索时,都有可能会出现重复计算。例如,下图中,当从值为 14 的位置出发时,它会找到一条长度为 9 的路径(如左图所示)。而当从值为13的位置出发时,它会找那条长度为 8 的路径(如右图所示)。

在这里插入图片描述

若假设顺序为 14、13、……。则在从 14 出发进行深度优先搜索时,它肯定会先计算从 13 出发得到的最长下降子序列;而从 13 出发时,它又会先计算从12出发得到的最长下降子序列……直到该过程结束,并在最后通过回溯得到从 14 出发得到的最长下降子序列长度。此时,当我们再计算 13 的最长子序列时,它也会重复上述过程,这便是大量重复计算的地方。试想,如果我们在求某个点的最长下降子序列时,对其搜索到的所有点的最长子序列长度进行存储,即:

在这里插入图片描述

这样一来,以 14 所在位置为起点涉及到的最长下降序列中的任何点都将在递归过程中找到其自身的最长下降子序列,并且形成记录。这就避免了后续重复寻找 13、12、……、2、1 等值所在位置的最长下降子序列。另一方面,假设现在要求值为 15 的位置处的最长下降子序列,则其只需要做一件事:判断当前这个点是否有更长的下降子序列?如果不是,则当前位置存储的值即为其最大值;否则,就对当前位置继续进行深搜。即,对值为 15 的位置 ( 1 ,   1 ) \left(1,\ 1\right) (1, 1) 而言,它只需做一件事:

p a t h ( 1 , 1 ) = max { p a t h ( 1 , 1 ) , D F S ( n e x t P o i n t ) + 1 } path(1,1)=\text{max}\{path(1,1) ,DFS(nextPoint)+1\} path(1,1)=max{path(1,1),DFS(nextPoint)+1}

其中 nextPoint 表示从点 ( 1 ,   1 ) \left(1,\ 1\right) (1, 1) 出发能走的下一个点(在给出的例子中, ( 1 ,   1 ) \left(1,\ 1\right) (1, 1) 的下一个可行点只有 ( 1 ,   2 ) \left(1,\ 2\right) (1, 2),而根据矩阵中存储的值可知,从点 ( 1 ,   2 ) \left(1,\ 2\right) (1, 2) 出发能得到的最长下降子序列长度为 9 ),于是有:

p a t h ( 1 , 1 ) = max { p a t h ( 1 , 1 ) , 10 } path(1,1)=\text{max}\{path(1,1) ,10\} path(1,1)=max{path(1,1),10}

显然,对位置 ( 1 ,   1 ) \left(1,\ 1\right) (1, 1) 而言,其默认最长下降子序列长度为 p a t h ( 1 , 1 ) = 1 path(1,1)=1 path(1,1)=1(即,仅含自身一个点)。因此,基于以上式子可以直接得到从 ( 1 ,   1 ) \left(1,\ 1\right) (1, 1) 出发的最长下降子序列长度为 10。

我们将以上这种记录前面搜索过程相关结果的搜索称为 “记忆化搜索”。在这种需要计算一个矩阵的最长(或最短)路径问题时,记忆化搜索可以大大降低计算成本。

下面直接给出基于以上思路得到的求解本题的完整代码:

/*
    MT2239 二维矩阵中的最长下降序列 
    深度优先搜索、记忆化搜索 
*/ 

#include<bits/stdc++.h> 
using namespace std;

const int N = 105;
// 存放矩阵的二维表,对应的访问标记表(记录该位置的最长下降序列长度) 
int matrix[N][N], vis[N][N];
// 矩阵规格
int n, m; 
// 四种行走方式(上、下、左、右) 
int forwardX[] = {0, 0, -1, 1};
int forwardY[] = {-1, 1, 0, 0};

// 录入矩阵信息 
void getMatrix()
{
    for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++)
            cin>>matrix[i][j];
}

// 获取指定位置的最长下降序列长度(带记忆化搜索) 
int DFS(int x, int y)
{
    // 如果已经被访问过,则直接返回该点记录的最长下降序列长度 
    if(vis[x][y]) return vis[x][y];
    // 否则初始化该点的最长下降序列长度为 1 (即自身) 
    vis[x][y] = 1;
    // 向各方向发散寻找最长下降序列
    for(int i=0; i<4; i++){
        int nextx = x + forwardX[i];
        int nexty = y + forwardY[i];
        // 越界或所处位置的数值更大则直接跳过 
        if(nextx < 1 || nextx > n || nexty < 1 || nexty > m || matrix[nextx][nexty] >= matrix[x][y])
            continue;
        // 否则将当前的最大下降子序列进行更新
        vis[x][y] = max(vis[x][y], DFS(nextx, nexty) + 1); 
    } 
    return vis[x][y];
}

// 获取最长下降序列  
int getLongestDesSeq()
{
    int tmp, longestDesSeq = 0;
    for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++){
            tmp = DFS(i, j);
            if(tmp > longestDesSeq)
                longestDesSeq = tmp;
        }
    return longestDesSeq;
}

int main( )
{
    // 录入数据 
    cin>>n>>m;
    getMatrix();
    // 寻找最长下降序列 
    cout<<getLongestDesSeq()<<endl;
    return 0;
}


MT2240 传染病

难度:钻石    时间限制:1秒    占用内存:128M
题目描述

小码哥正在研究传染病的相关知识,现在遇到了这么一个问题:有一个长为 n n n ,宽为 m m m ,高为 k k k (层数)的的长方体,它可以看成 n × m × k n\times m\times k n×m×k 1 × 1 × 1 1\times1\times1 1×1×1 大小的单位正方体。每个单位正方体都有一个字符。.表示该位置防疫措施不好,如果被传染了就会感染病毒。# 表示该区人民防疫措施良好,永远不会感染病毒(甚至都不会成为病毒的携带者)。

现在在长方体的最顶层(层号为 1)某个位置,因实验室泄露爆发了病毒。每过一个单位时间,病毒将扩散至周围 6 个方向的地区。如果某个地区为 #,那么一定不会被感染并且不会携带病毒同时也不会传播病毒。(但如果这个地方存在泄露的实验室,那么哪怕防疫措施再好也会被感染以及传播病毒)。

现在小码哥需要知道,假如第一层坐标为 ( x ,   y ) \left(x,\ y\right) (x, y) 的实验室泄露了病毒,那么最多会有多少区域被感染。

格式

输入格式:第一行三个正整数 k ,   n ,   m   ( k ,   n ,   m ≤ 10 ) k,\ n,\ m\ (k,\ n,\ m\le10) k, n, m (k, n, m10)
     接下来 k k k 层,每层 n n n 行,每行 m m m 个字符,表示长方体每个单位的状态;
      最后一行两个正整数 x ,   y   ( x ≤ n ,   y ≤ m ) x,\ y\ \left(x\le n,\ y\le m\right) x, y (xn, ym) 表示发生泄漏的实验室的坐标(这个实验室在第一层)。

输出格式:按题目要求输出一行一个整数表示答案。

样例 1

输入:

3 3 3

.#.
###
##.

.##
###
##.

...
...
...

1 1

输出:

13


相关知识点:广度优先搜索


题解


这道题实际上就是基于广度优先搜索的走格子问题,不同的是这道题里的空间是一个三维空间。为此,我们只需要在BFS中再额外地添加两种行走方式,然后从题目给出的起点进行行走,并逐个统计感染的实验室个数即可。另外,由于广度优先搜索需要用到队列这一数据结构,故为了方便使用我们单独定义了一个类似“三元组”形式的数据结构(结构体),用于表示三维空间中的一个点。

下面直接给出求解本题的完整代码:

/*
    MT2240 传染病 
    广度优先搜索 
*/ 

#include<bits/stdc++.h> 
using namespace std;

const int N = 13;
// 存放矩阵的二维表,对应的访问标记表
char cube[N][N][N];
int vis[N][N][N];
// 立方体规格
int k, n, m; 
// 六种行走方式(上、下、左、右、前、后) 
int forwardX[] = {0, 0, -1, 1, 0, 0};
int forwardY[] = {0, 0, 0, 0, -1, 1};
int forwardZ[] = {-1, 1, 0, 0, 0, 0};
// 封装三维空间中一个点的数据结构
struct Site{
    int x, y, z;
    Site(){}
    Site(int x, int y ,int z):x(x),y(y),z(z){}
}; 
Site curSite, nextSite;

// 录入立方体信息 
void getCube()
{
    for(int l=1; l<=k; l++)
        for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++)
                cin>>cube[i][j][l];
}

// 检测下个区域是否可达 
bool check(Site site){
    int x = site.x, y = site.y, z = site.z;
    // 测下个区域是否合法
    if(x<1 || x>n || y<1 || y>m || z<1 || z>k) 
        return false;
    // 测下个区域能否继续传播病毒
    if(vis[x][y][z] || cube[x][y][z] == '#')
        return false;
    return true;
} 

// 计被感染的区块个数(广度优先搜索) 
int getInfectedBlocks(int x, int y)
{
    int infectedBlocks = 0;
    // 初始化队列 
    queue<Site> q;
    q.push(Site(x,y,1));
    vis[x][y][1] = true;
    // 广度优先搜索
    while(!q.empty()){
        // 取出当前队头元素 
        curSite = q.front();
        // 队头元素出队列
        q.pop();
        // 统计被感染数 
        infectedBlocks++;
        // 病毒扩散! 
        for(int i=0; i<6; i++){
            // 扩散后的新位置 
            nextSite.x = curSite.x + forwardX[i];
            nextSite.y = curSite.y + forwardY[i];
            nextSite.z = curSite.z + forwardZ[i];
            // 若新扩散成立
            if(check(nextSite)) {
                // 则入队列 
                q.push(nextSite);
                // 置访问标记为真
                vis[nextSite.x][nextSite.y][nextSite.z] = true;
            }
        }
    }
    return infectedBlocks;
}

int main( )
{
    // 录入数据 
    int x, y; 
    cin>>k>>n>>m;
    getCube();
    cin>>x>>y;
    // 统计被感染的区块个数 
    cout<<getInfectedBlocks(x, y)<<endl;
    return 0;
}


MT2241 循环空间

难度:钻石    时间限制:1秒    占用内存:256M
题目描述

给出一个二维的由0、1、2组成的 n × n n\times n n×n 空间,0 表示障碍物,1 表示可行走,2 表示起点,这个空间是循环的,如走到左边界再往左走,你会出现在右边界对应位置,我们每次能向上、下、左、右中的一个方向行走一格。

请输出从起点到每个点的最短路径长度。

格式

输入格式:第一行一个正整数 n n n ,表示地图的边长;
     接下来 n n n 行,每行 n n n 个整数,0 表示障碍物,1 表示可行走,2 表示起点。
输出格式:输出 n n n 行,每行 n n n 个整数。0 表示起点或无法到达的点,其他数字表示从起点到此处的最短路径长度。

样例 1

输入:
5
0 1 2 1 0
0 0 1 0 1
1 1 0 0 1
1 0 0 1 0
1 1 1 0 0

输出:

0 1 0 1 0
0 0 1 0 7
5 6 0 0 6
4 0 0 0 0
3 2 1 0 0

备注

对于30%的数据: 1 ≤ n ≤ 30 1\le n\le30 1n30
对于60%的数据: 1 ≤ n ≤ 300 1\le n\le300 1n300
对于100%的数据: 1 ≤ n ≤ 2000 1\le n\le2000 1n2000


相关知识点:广度优先搜索


题解


求二维矩阵中指定点到其余所有点的最短距离,一道典型的广度优先搜索题。不过需要注意的是,本题给出的二维矩阵是 “循环” 的,即处于边界位置并继续往边界方向行走时,会直接走到另一个相对的边界。这相当于是对“行走方式”添加了一种新的准则。我们的处理办法也很简单,将新位置在各维度上的值加上整个二维矩阵的规格(避免了向索引降低的方向行走时出现负的索引),并对该规格取余(避免出现索引出现超过矩阵规格的情况)。

由于本题涉及到的是一个二维空间,因此我们直接利用 STL 提供的模板数据结构 pair 作为 BFS 中队列的基本数据类型。下面直接给出求解本题的完整代码:

/*
    MT2241 循环空间 
    广度优先搜索(必须快读输入数据,否则会超时 3/10 的数据) 
*/ 

#include<bits/stdc++.h> 
using namespace std;

const int N = 2005;
// 存放矩阵的二维表,对应的访问标记表(到该点的最短路径)
int matrix[N][N], vis[N][N];
// 矩阵规格,起始点坐标 
int n, startX, startY;
// 当前位置和下一个状态所处位置
pair<int, int> curPoint, nextPoint;
// 四种行走方式(上、下、左、右) 
int forwardX[] = {0, 0, -1, 1};
int forwardY[] = {-1, 1, 0, 0};

// 录入矩阵信息 
void getMatrix()
{
    for(int i=0; i<n; i++)
        for(int j=0; j<n; j++){
            cin>>matrix[i][j];
            if(matrix[i][j] == 2){
                startX = i;
                startY = j; 
            }
        }
}

// 获取从指定位置出发到各点的最短路径长度 
void BFS(int x, int y)
{
    // 初始化访问标记表
    memset(vis, -1, sizeof(vis));
    // 初始化队列 
    queue<pair<int,int> > q;
    // 创建当前的点位置
    curPoint = make_pair(x,y); 
    // 入队列 
    q.push(curPoint);
    // 重置访问标记 
    vis[x][y] = 0;
    // 广度优先搜索
    while(!q.empty()){
        // 取出当前队头元素 
        curPoint = q.front();
        // 队头元素出队列
        q.pop();
        // 向四个方向行走 
        for(int i=0; i<4; i++){
            // 新位置 
            nextPoint = make_pair((curPoint.first+forwardX[i]+n) % n, 
                (curPoint.second+forwardY[i]+n) % n);
            // 如果新位置未被访问过且可行走,则更新其值
            if(vis[nextPoint.first][nextPoint.second] < 0 && matrix[nextPoint.first][nextPoint.second] == 1){
                vis[nextPoint.first][nextPoint.second] = vis[curPoint.first][curPoint.second] + 1; 
                // 入队列
                q.push(nextPoint);
            }
        }
    }
    // 整个广度优先搜索结束后,剩余所有尚未被访问的位置将无法到达
    for(int i=0; i<n; i++)
        for(int j=0; j<n; j++)
            if(vis[i][j] < 0)
                 vis[i][j] = 0;
}

// 输出最短路径长度矩阵 
void printPathLength()
{
    for(int i=0; i<n; i++){
        for(int j=0; j<n; j++)
            cout<<vis[i][j]<<" ";
        cout<<endl;
    } 
} 

int main( )
{
    // 快读输入数据 
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    // 录入数据 
    cin>>n;
    getMatrix();
    // 寻找从指定点出发的最短路径 
    BFS(startX, startY);
    // 输出指定点到所有点的最短路径长度
    printPathLength(); 
    return 0;
}



END


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

theSerein

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值