【搜索】DFS搜索顺序

算法提高课笔记

目录

  • 马走日
    • 题意
    • 思路
    • 代码
  • 单词接龙
    • 题意
    • 思路
    • 代码
  • 分成互质组
    • 题意
    • 思路
    • 代码

马走日

原题链接

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

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

输入格式

第一行为整数 T,表示测试数据组数。

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

输出格式

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

数据范围

1 ≤ T ≤ 9,
1 ≤ m , n ≤9,
1 ≤ n × m ≤ 28,
0 ≤ x ≤ n − 1,
0 ≤ y ≤ m − 1

输入样例

1
5 4 0 0

输出样例

32

题意

给出矩阵大小,给出马的初始位置,马只能走日。问有多少种方案让马可以遍历完棋盘上的所有点,每种方案里不可以重复经过两个点

思路

这就是典型的外部搜索,是不同状态之间的搜索,因此每次需要恢复现场(可以理解为悔棋)

代码

#include 

using namespace std;

const int N = 10;

int n, m;
bool st[N][N]; // 判重
int ans;

int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
int 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] = true;

    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] = false;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

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

        memset(st, 0, sizeof st);
        ans = 0;
        dfs(x, y, 1);

        cout << ans << '\n';
    }
}

单词接龙

原题链接

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

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

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

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

输入格式

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

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

输出格式

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

数据范围

n ≤ 20,
单词随机生成。

输入样例

5
at
touch
cheat
choose
tact
a

输出样例

23

提示

连成的“龙”为 atoucheatactactouchoose。

题意

给出多个字符串,首位有相同字串的两个字符串可以连接,给出开头字符,问能连接的最大长度

思路

外部搜索,每次需恢复原状

从开头字符与给定字符相同的单词开始,每次遇到能接到字符串后面的就往深遍历,(形成一个搜索树一样的结构)

下方代码有详细注释

代码

#include 

using namespace std;

const int N = 21;

int n;
string word[N]; // 记录每个单词
int g[N][N]; // 记录每两个单词有多少重合
int used[N]; // 记录这个单词被用了多少次
int ans;

void dfs(string dragon, int last) // 第一个参数是当前字符串 第二个参数是当前最后一个字符串编号
{
    ans = max((int)dragon.size(), ans); // 更新最大长度

    used[last] ++ ; // 更新字符串使用次数

    for (int i = 0; i < n; i ++ ) // 遍历每个字符串
        if (g[last][i] && used[i] < 2) // 条件:和当前最后一个字符串有重合 && 使用次数不到2次
            dfs(dragon + word[i].substr(g[last][i]), i);

    used[last] -- ; // 恢复
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n;
    for (int i = 0; i < n; i ++ ) cin >> word[i];
    char start;
    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 ++ ) // 从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 << '\n';
}

分成互质组

原题链接

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

至少要分成多少个组?

输入格式

第一行是一个正整数 n。

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

输出格式

一个正整数,即最少需要的组数。

数据范围

1 ≤ n ≤ 10

输入样例

6
14 20 33 117 143 175

输出样例

3

题意

每个组的数要互质,给出一系列数,问最少多少组

思路

按照组合的方式搜索,每个组里按照下表从小到大添加所有能加进去的元素,直到不能再加任何一个元素,就新开一个组

当某个数可以加到最后一组时,就没有必要新开一个组

下方代码中有详细注释

代码

#include 

using namespace std;

const int N = 10;

int n;
int p[N]; // 存所有元素
int group[N][N]; // 存每个组以及其中元素
bool st[N]; // 判重
int ans = N;

int gcd(int a, int b) // 找最大公约数
{
    return b ? gcd(b, a % b) : a;
}

bool check(int group[], int gc, int i)
{
    for (int j = 0; j < gc; j ++ ) // 遍历group中的每个元素
        if (gcd(p[group[j]], p[i]) > 1) // 最大公约数大于1说明不是互质
            return false;
    return true;
}

void dfs(int u, int gc, int tc, int start) // u:第几组 gc:当前组内下标 tc:当前一共有多少元素 start:当前这一组从哪个元素开始搜
{
    if (u >= ans) return; // 剪枝优化:如果当前组数已经大于等于ans 说明一定不是最优解 直接返回
    if (tc == n) ans = u; // 所有数都搜索完了

    bool flag = true; // true表示当前组不能继续添加新元素
    for (int i = start; i < n; i ++ ) // 从start开始遍历
        if (!st[i] && check(group[u], gc, i)) // 该元素没用过且与当前组所有元素互质
        {
            st[i] = true; // 标记该元素
            group[u][gc] = i; // 将该元素加入组中

            dfs(u, gc + 1, tc + 1, i + 1); // 下一层遍历

            st[i] = false; // 恢复现场
            flag = false; // 表示还能添加新元素
        }

    if (flag) dfs(u + 1, 0, tc, 0); // 不能添加新元素时新开一个组
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n;
    for (int i = 0; i < n; i ++ ) cin >> p[i];

    dfs(1, 0, 0, 0);

    cout << ans << '\n';
}

你可能感兴趣的:(搜索,深度优先,算法)