玩命加载中 . . .

洛谷官方题单——搜索题解


搜索一般我们都分为DFS和BFS,其中设计到回溯,剪枝的技巧。

但是一般我们都将搜索视作暴力,但是比纯粹的暴力会快不少,并且可以通过搜索获得小规模问题从而帮助推导后续的问题。

最后提醒一下,DFS是存在最大值性质的,BFS是存在最小值性质的,因此除非题目挖坑,否则不要轻易的用BFS解决最小值问题。

题目0:八皇后 Checker Challenge

八皇后问题是学习数据结构的时候的必学问题了,实际上考察的是DFS+剪枝,因为有些方法在搜索过程中,已经违反了题目的要求,没有必要继续搜索下去了。注意回溯的时候记得恢复状态

#pragma warning(disable:4996)
#include<stdio.h>
int ans[20], book[3][30], sum, n;
void dfs(int x){
    if (x > n){
        if (++sum > 3) return;
        else{
            for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
            printf("\n");
            return;
        }
    }
    for (int i = 1; i <= n; i++){
        if ((!book[0][i]) && (!book[1][x + i]) && (!book[2][x + n - i])) {
            //同一行,同一列,同一对角线,其中同一对角线可以考虑结合直线的一次方程
            ans[x] = i;
            book[0][i] = 1; book[1][x + i] = 1; book[2][x + n - i] = 1;
            dfs(x + 1);
            book[0][i] = 0; book[1][x + i] = 0; book[2][x + n - i] = 0;
        }
    }
}
int main(){
    scanf("%d", &n);
    dfs(1);
    return 0 * printf("%d\n", sum);
}

题目1:kkksc03考前临时抱佛脚

原题,略。

题目2:马的遍历

BFS模板题,只需要存储马可以跑到的地方然后逐个填数字即可,注意最后需要把起点归0。

#include<queue>
#include<string.h>
#include<stdio.h>
using namespace std;
typedef long long ll;
int xx[8] = { 2, -2, 2, -2, -1, 1, -1, 1 };
int yy[8] = { 1, 1, -1, -1, 2, 2, -2, -2 };
int n, m, a, b, map[402][402];
struct Node {
    int x, y, s;
};
int main() {
    scanf("%d%d%d%d", &n, &m, &a, &b);
    queue<Node>q;
    map[a][b] = 0;
    memset(map, -1, sizeof(int) * 161604);
    Node t;
    t.x = a, t.y = b, t.s = 0;
    q.push(t);
    while (!q.empty()) {
        for (int i = 0; i < 8; i++) {
            int dx = q.front().x + xx[i];
            int dy = q.front().y + yy[i];
            if (dx >= 1 && dx <= n && dy >= 1 && dy <= m && map[dx][dy] == -1) {
                Node temp;
                temp.x = dx, temp.y = dy, temp.s = q.front().s + 1;
                q.push(temp);
                map[dx][dy] = q.front().s + 1;
            }
        }
        q.pop();
    }
    map[a][b] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++)
            printf("%-5d", map[i][j]);
        printf("\n");
    }
    return 0;
}

题目3:奇怪的电梯

要找最小值,首先想到使用宽搜的方法,即我们将开始的地方放入队列,然后我们得到队头元素,然后按照题目要求到达能到(上和下)的地方,并且将这个地方入队。一直循环到队列空,那么到时候ans存储的就是所需要的按钮次数。

#include<queue>
#include<stdio.h>
using namespace std;
int n, a, b, F[205];
bool vis[205];
struct Floor { 
    int id, step; 
}ans;
int main(){
    scanf("%d%d%d", &n, &a, &b);
    queue<Floor> q;
    for (int i = 1; i <= n; i++) scanf("%d", &F[i]);
    Floor temp{ a,0 };
    q.push(temp);
    while (q.size()){
        ans = q.front(); 
        q.pop();
        if (ans.id == b) break;
        if (ans.id + F[ans.id] <= n && !vis[ans.id + F[ans.id]]){
            Floor temp_1{ ans.id + F[ans.id], ans.step + 1 };
            q.push(temp_1);
            vis[ans.id + F[ans.id]] = 1;
        }
        if (ans.id - F[ans.id] >= 1 && !vis[ans.id - F[ans.id]]){
            Floor temp_2{ ans.id - F[ans.id], ans.step + 1 };
            q.push(temp_2);
            vis[ans.id - F[ans.id]] = 1;
        }
    }
    if (ans.id == b) return 0 * printf("%d\n", ans.step);
    else return 0 * printf("-1");
}

题目4:Meteor Shower S

最少时间,依旧需要宽搜,但是这道题是属于很多细节的题,所以看代码的注释把。

#include<queue>
#include<stdio.h>
#include<string.h>
using namespace std;
struct Meteor{
    int x, y, time;
}; 
int t[305][305], book[305][305]; 
int dx[4] = { 0,0,1,-1 }, dy[4] = { 1,-1,0,0 };
queue<Meteor>q;
int main(){
    int m;
    scanf("%d", &m);
    memset(t, -1, sizeof(t));
    for (int i = 1; i <= m; i++){
        int temp_x, temp_y, temp_t;
        scanf("%d%d%d", &temp_x, &temp_y, &temp_t);
        if (temp_t < t[temp_x][temp_y] || t[temp_x][temp_y] == -1)
            t[temp_x][temp_y] = temp_t;
        for (int i = 0; i < 4; i++){
            int nx = temp_x + dx[i], ny = temp_y + dy[i];
            if (nx >= 0 && ny >= 0 && (t[nx][ny] == -1 || temp_t < t[nx][ny]))
                t[nx][ny] = temp_t; 
        }
    }
    Meteor begin{ 0,0,0 };
    book[0][0] = 1;
    q.push(begin);
    while (!q.empty()){
        Meteor p = q.front();
        q.pop();
        for (int i = 0; i < 4; i++){
            int now_x = p.x + dx[i], now_y = p.y + dy[i];
            if (now_x >= 0 && now_y >= 0 && book[now_x][now_y] == 0 && (t[now_x][now_y] == -1 || p.time + 1 < t[now_x][now_y])){
                Meteor ans{ now_x ,now_y ,p.time + 1 };
                book[now_x][now_y] = 1;
                q.push(ans);
                if (t[now_x][now_y] == -1) return 0 * printf("%d\n", ans.time);
            }
        }
    }
    return 0 * printf("-1\n");
}

题目5:选数

原题,略。

题目6:PERKET

原题,略。

题目7:吃奶酪

原题,略。

题目8:迷宫

要找到所有的通路,那么就要搜索所有的可能,找某条道路的方法是DFS,所以我们DFS后,ans++即可。

#include<stdio.h>
#include<string.h>
using namespace std;
int map[6][6];
bool temp[6][6];
int dx[4] = { 0,0,1,-1 };
int dy[4] = { -1,1,0,0 };
int ans, end_x, end_y, begin_x, begin_y;
void dfs(int x, int y){
    if (x == end_x && y == end_y){
        ans++;
        return;
    }
    else{
        for (int i = 0; i <= 3; i++){
            if (temp[x + dx[i]][y + dy[i]] == 0 && map[x + dx[i]][y + dy[i]] == 1){
                temp[x][y] = 1;
                dfs(x + dx[i], y + dy[i]);
                temp[x][y] = 0;
            }
        }
    }
}
int main(){
    int N, T, M, ob_x, ob_y;
    scanf("%d%d%d%d%d%d%d", &N, &M, &T, &begin_x, &begin_y, &end_x, &end_y);
    for (int i = 1; i <= N; i++)
        for (int j = 1; j <= M; j++) map[i][j] = 1;
    for (int i = 1; i <= T; i++){
        scanf("%d%d", &ob_x, &ob_y);
        map[ob_x][ob_y] = 0;
    }
    dfs(begin_x, begin_y);
    return 0 * printf("%d\n", ans);
}

题目9:单词接龙

找到最长的字符串,那么我们就联想到DFS。维护一个全局变量ans,然后每次进入一层DFS都直接和之前的ans和的temp_ans取最大值,最后结束即可。

难点在于重叠的处理,check函数即可。

#include <iostream>
#include <string>
using namespace std;
int ans = 0, n, dic_cnt[30];
string dic[30], start;
int max(int x, int y) { return x > y ? x : y; }
bool check(string origin, string checked, int check_start) {
    int len = origin.length();
    for (int i = 0; i < check_start; i++) 
        if ((len - check_start + i < 0) || (origin[len - check_start + i] != checked[i])) 
            return false;
    return true;
}
void add(string& origin, string added, int add_start) {
    int len = added.length();
    for (int i = add_start; i < len; i++) origin += added[i];
}
void dfs(string now){
    int x = now.length();
    ans = max(ans, x);
    for (int i = 1; i <= n; i++) {
        if (dic_cnt[i] >= 2) continue;
        int len = dic[i].length();
        for (int j = 1; j <= len; j++) {
            if (check(now, dic[i], j)) {
                string temp = now;
                add(temp, dic[i], j);
                if (temp == now) continue;
                dic_cnt[i]++;
                dfs(temp);
                dic_cnt[i]--;
            }
        }
    }
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> dic[i];
    cin >> start;
    dfs(start);
    cout << ans << endl;
    return 0;
}

题目10:单词方阵

DFS+八方向检查,DFS接口额外增加一个项确定方向,代码有坑请谨慎复制。

#include<iostream>
struct Cordinate{
    int x, y;
}memo[10];
char map[105][105], key[] = "yizhong";
int book[105][105];
int d[8][2] = { {-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1} };
void dfs(int x, int y, int direct, int cur){
    if (cur == 7) {
        for (int i = 0; i < 7; i++)
            book[memo[i].x][memo[i].y] = 1;
    }
    else {
        int dx = x + d[direct][0], dy = y + d[direct][1];
        if (cur == 6 || map[dx][dy] == key[cur + 1]) {
            memo[cur].x = x, memo[cur].y = y;
            dfs(dx, dy, direct, cur + 1);
        }
    }
}
int main(){
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%s", map[i]);
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (map[i][j] == 'y') {
                for (int k = 0; k < 8; k++) {
                    int x = i + d[k][0], y = j + d[k][1];
                    if (map[x][y] == 'i') dfs(i, j, k, 0);
                }
            }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++)
            if (book[i][j]) printf("%c", map[i][j]);
            else printf("*");
        printf("\n");
    }
    return 0;
}

题目11:自然数拆分

典型的深搜题目,粗暴的不能再粗暴了。

#include<stdio.h>
int n, ans[11] = { 1 }, m;
void print(int num) {
    for (int i = 1; i < num; i++) printf("%d+", ans[i]);
    printf("%d\n", ans[num]);
}
void dfs(int len) {
    for (int i = ans[len - 1]; i <= m; i++) {
        if (i == n) continue;
        ans[len] = i, m -= i;
        if (m == 0) print(len);
        else dfs(len + 1);
        m += i;
    }
}
int main() {
    scanf("%d", &n);
    m = n;
    dfs(1);
    return 0;
}

题目12:Lake Counting S

连通块的典型题目,BFS和DFS均可,就看自己怎么玩。

下面是BFS,但是TLE了别怪我嗷。

#pragma warning(disable:4996)
#include<stdio.h>
#include<queue>
using namespace std;
struct cordinate {
    int x, y;
};
queue<cordinate> q;
int dx[8] = { 1,0,-1,0,1,1,-1,-1 };
int dy[8] = { 0,1,0,-1,1,-1,-1,1 };
int n, m, ans;
char map[105][105];
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++) scanf("%s", map[i]);
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (map[i][j] == 'W') {
                cordinate temp{ i,j };
                q.push(temp);
                while (!q.empty()) {
                    map[q.front().x][q.front().y] = '.';
                    for (int k = 0; k < 8; k++) {
                        int now_x = q.front().x + dx[k], now_y = q.front().y + dy[k];
                        if (now_x >= 0 && now_x < n && now_y >= 0 && now_y < m && map[now_x][now_y] == 'W') {
                            cordinate temp{ now_x,now_y };
                            q.push(temp);
                        }
                    }
                    q.pop();
                }
                ans++;
            }
        }
    }
    return 0 * printf("%d\n", ans);
}

DFS

#include<iostream>
#include<stdio.h>
using namespace std;
int dx[8] = { -1,-1,-1,0,0,1,1,1 };
int dy[8] = { -1,0,1,-1,1,-1,0,1 };
int n, m, ans;
char map[105][105];
void dfs(int x, int y){
    int now_x, now_y;
    map[x][y] = '.';
    for (int i = 0; i < 8; i++){
        int now_x = x + dx[i], now_y = y + dy[i];
        if (now_x<1 || now_x>n || now_y<1 || now_y>m || map[now_x][now_y] == '.') continue;
        map[now_x][now_y] = '.';
        dfs(now_x, now_y);
    }
}
int main(){
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> map[i][j];//为了不想处理空格不择手段
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            if (map[i][j] == 'W'){
                ans++;
                dfs(i, j);
            }
        }
    }
    return 0 * printf("%d\n", ans);
}

题目13:填涂颜色

DFS+染色,也是搜索的基本用途了。

#include<stdio.h>
int book[32][32], map[32][32];
int dx[4] = { -1,1,0,0 };
int dy[4] = { 0,0,-1,1 };
int n;
void dfs(int x, int y) {
    if (x<0 || x>n + 1 || y<0 || y>n + 1 || book[x][y] != 0) return;
    book[x][y] = 1;
    for (int i = 0; i < 4; i++) dfs(x + dx[i], y + dy[i]);
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) {
            scanf("%d", &map[i][j]);
            if (map[i][j] == 0) book[i][j] = 0;
            else book[i][j] = 2;
        }
    dfs(0, 0);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++)
            if (book[i][j] == 0) printf("2 ");
            else printf("%d ", map[i][j]);
        printf("\n");
    }
    return 0;
}

题目14:字串变换

讲真他不放在这个踢单我真会认为是一个字符串写自动机的问题- -。

首先是最小步骤,因此我们应该优先想到BFS。同时由于STL=sometimes time limit,所以这个地方为了防止TLE应当手写队列。

其核心就是字符串入队看一下有无可修改方法,把修改之后的字符串入队列,记录修改的次数。

当然如果你不想用string的库函数,那么您可以手写KMP。

#pragma warning(disable:4996)
#include<iostream>
#include<string>
#include<stdio.h>
using namespace std;
string a, b, from[7], to[7];
struct Queue {
    string cur;
    int cnt;
}q[2000000];
int main(){
    cin >> a >> b;
    int cnt = 1, q_head = 0, q_tail = 1;
    while (cin >> from[cnt] >> to[cnt])cnt++;
    cnt--, q[q_tail].cur = a, q[q_tail].cnt = 0;
    while (q_head < q_tail){
        q_head++;
        if (q[q_head].cnt > 10) return 0 * printf("NO ANSWER!\n");
        for (int j = 1; j <= cnt; j++){
            int change_pos = q[q_head].cur.find(from[j], 0);
            while (1){
                if (change_pos == -1) break;
                else{
                    q_tail++;
                    q[q_tail].cur = q[q_head].cur, q[q_tail].cnt = q[q_head].cnt + 1;
                    q[q_tail].cur.replace(change_pos, from[j].size(), to[j]);
                    if (q[q_tail].cur == b) return 0 * printf("%d\n", q[q_tail].cnt);
                    change_pos = q[q_head].cur.find(from[j], change_pos + 1);
                }
            }
        }
    }
    return 0;
}

题目15:Corn Maze S

很简单细节很多的BFS(因为要找最短时间),简单来说就是:判断装置自己判断自己,导致死循环,地图的处理以及其他乱七八糟的小坑点。

#include<stdio.h>
#include<iostream>
using namespace std;
int map[500][500], book[500][500], n, m, end_x, end_y, trans_pair_x, trans_pair_y;
int dx[4] = { -1,1,0,0 };
int dy[4] = { 0,0,-1,1 };
struct node{
    int x, y, time;
} ans[100050];
void find_trans(int x, int y){
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            if (!(i == x && j == y)){
                if (map[i][j] == map[x][y]){
                    trans_pair_x = i, trans_pair_y = j;
                    return;
                }
            }
        }
    }
}
int bfs(){
    int head = 0, tail = 1;
    do{
        head++;
        if (map[ans[head].x][ans[head].y] >= 'A' && map[ans[head].x][ans[head].y] <= 'Z'){
            find_trans(ans[head].x, ans[head].y);
            ans[head].x = trans_pair_x;
            ans[head].y = trans_pair_y;
        }
        for (int i = 0; i < 4; i++){
            int now_x = ans[head].x + dx[i], now_y = ans[head].y + dy[i];
            if (map[now_x][now_y] != 0 && book[now_x][now_y] == 0){
                book[now_x][now_y] = 1;
                tail++;
                ans[tail].x = now_x, ans[tail].y = now_y, ans[tail].time = ans[head].time + 1;
                if (now_x == end_x && now_y == end_y)return 0 * printf("%d\n", ans[tail].time);
            }
        }
    } while (head < tail);
}
int main(){
    char s;
    int begin_x, begin_y;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            cin >> s;
            if (s == '.') map[i][j] = 1;
            if (s >= 'A' && s <= 'Z') map[i][j] = s;
            if (s == '@'){
                begin_x = i, begin_y = j;
                map[i][j] = 1;
            }
            if (s == '='){
                end_x = i, end_y = j;
                map[i][j] = 1;
            }
        }
    }
    book[begin_x][begin_y] = 1, ans[1].x = begin_x, ans[1].y = begin_y;
    bfs();
    return 0;
}

三位数以1开头的只剩下四个数据结构的内容了,说明入门提单快刷完了(洛谷官方题单用的国外命名方式,因此1开头是基础,2开头是进阶。水题就快刷没了(贼难受)。

下一次更新就是数据结构的线性表了,实际上这部分的题就是基本的四个线性表:数组,链表,栈,队列的应用,里面或多或少牵扯到指针和其他的细节,反正不难便是了。


文章作者: AleXandrite
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 AleXandrite !
  目录