[kuangbin带你飞] 专题一 简单搜索 题解(超详细注释,史上最强题解)

前言

[kuangbin带你飞]是ACMer中名气最大的题单之一,起源是上海大学的kuangbin大佬,是从基础到进阶阶段的最好题单之一,虽然题量适中,题解繁多,不过大多冗杂,注释偏少。

本专栏致力于用尽量简短的描述,以及大量的代码注释,帮助同学们真正理解题目的含义与解题方法,详细地解析代码,从而帮助同学们提高代码能力(当然还是要自己动手),让同学们体验到算法的乐趣。

本专栏会持续更新迭代,欢迎各位算法爱好者提供意见与建议,从而督促本人做出更好的题解。能够迭代升级的,才是最强的题解。


相信大多数看这个题解的同学均是已经接触到kuangbin专题的了,但是为了让同学们学习路径更平滑,我还是想稍作补充:

包含kuangbin专题在内的,ACMer学习路径(必看):ACM 的正确入门方式是什么?

kuangbin专题合集,最全的合集:[kuangbin带你飞]专题1-23,本专题所有题目均在上面提交

kuangbin专题合集,ACwing评测(好处是不用忍受老OJ的老旧评测机,坏处是暂时没有更新完所有专题)


注意!kuangbin每个专题需要一定的知识基础,所以在学习之前希望各位同学有一定的学习途径。

以下是我常用的学习途径:

  1. (首推)ACwing算法基础课 + 算法提高课,性价比最高的网课,包含大量基础到进阶的知识点,强烈推荐入手,既可当算法百科查阅,也可系统学习打好基础。
  2. 《算法竞赛进阶指南》,非常契合kuangbin专题的一本书,既有很多知识点讲解,同时也提供了很多例题帮助理解,也是个很不错的题单,牛客 ACwing 上都可以刷题。
  3. OIWIKI,相当全的知识点介绍网站,可以通过这个网站初步了解知识点的原理与基本实现。

注意!本专栏的所有题解都会写出所有题目的主要知识点,方便同学们查找学习


A.棋盘问题

主要知识点:DFS,枚举

题意概括:

给定一个 n × n n\times n n×n 正方形的迷宫,其中每一行和每一列只能放一个棋子,而且只有特定的 “#” 号格子,才允许放一颗棋子

解题思路:

类似经典的“八皇后”问题,只不过加上了只有特定位置才能放棋子这一特点

最朴素的方式是用DFS枚举所有 “#” 号格子,如果同一行或同一列没有放过棋子(用一维数组记录这一行/列是否放过棋子进行标记),才会放下棋子

也可以像我这样,仅枚举每行的 ”#“ 号格子,节约了记录行的空间。

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 10 + 10;

int n, k;
char g[N][N];		// 记录迷宫
int col[N];			// 对列上是否存在棋子进行标记
int ans;

void dfs(int row, int cnt)	// row是当前行,cnt是已使用的棋子数
{
    if (cnt == k)	// 已经使用k颗棋子,该分支无需继续(剪枝)
    {
        ans ++;
        return;
    }
    if (row == n) return;	// 所有行都枚举完了,而棋子数还未达条件
    for (int i = 0; i < n; i ++ )
    {
        // 枚举从所有列上的区域
        // 只有当前格子可以放棋子,且当前列上没有棋子,才能尝试放下这颗棋子
        if (g[row][i] == '#' && !col[i])
        {
            col[i] = 1;	// 在当前列做标记
            // 放下这颗棋子,并枚举下一行
            dfs(row + 1, cnt + 1);	
            col[i] = 0;	// 这列不放棋子,回到原状态
        }
    }
    // 这一行不放棋子,继续枚举下一行
    dfs(row + 1, cnt);
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    while(cin >> n >> k && n != -1 )
    {
        ans = 0;
        memset(col, 0, sizeof col);
        // 行数从0 ~ n - 1
        for (int i = 0; i < n; i ++ ) cin >> g[i];
        // 初始值,第0行开始,当前一共使用了0颗棋子
        dfs(0, 0);
        cout << ans << endl;
    }
    // fclose(stdout);//输出结束
    return 0;
}

B.Dungeon Master(地牢大师)

主要知识点:BFS求最短路

题意概括:

给定一个 L × R × C L \times R \times C L×R×C 大小的3D地牢,其中 “S” 点为起点, “E” 点为终点,每单位时间能够向一个方格移动一格,包括上下层,无法进入 "#"号 格子。求能否到达终点,如果能到达,最小的步数是多少?

解题思路:

很明显的BFS问题,从起点开始,一步一步扩散,直到遍历到终点或者遍历完所有可到达的点而不能到达终点。

这道题除了代码量稍微大一点之外,就是一道正常的BFS题

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 30 + 10;

struct Position
{
    int floor, x, y;
};

int L, R, C;
char g[N][N][N];
int dist[N][N][N];

int bfs(Position st, Position ed)	// st为起点,ed为终点
{
    // 前后左右方向的向量
    int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
    // 用队列存储当前坐标
    queue<Position> q;
    q.push(st);
    int floor = st.floor, x = st.x, y = st.y;
    dist[floor][x][y] = 0;
    while (q.size())
    {
        Position t = q.front(); q.pop();
        floor = t.floor, x = t.x, y = t.y;
        if (g[floor][x][y] == 'E') return dist[floor][x][y]; 
        for (int i = -1; i <= 1; i += 2)
        {
            if (floor + i < 0 || floor + i >= L) continue;
            if (dist[floor + i][x][y] != -1) continue;
            if (g[floor + i][x][y] == '#') continue;
            dist[floor + i][x][y] = dist[floor][x][y] + 1;
            // 由于POJ的古老评测机,这里必须有一个中间变量才能编译
            Position k = {floor + i, x, y};
            q.push(k);
        }
        for (int i = 0; i < 4; i ++ )
        {
            int a = x + dx[i], b = y + dy[i];
            if (a < 0 || a >= R || b < 0 || b >= C) continue;
            if (g[floor][a][b] == '#') continue;
            if (dist[floor][a][b] != -1) continue;
            dist[floor][a][b] = dist[floor][x][y] + 1;
            // 由于POJ的古老评测机,这里必须有一个中间变量才能编译
            Position k = {floor, a, b};
            q.push(k);
        }
    }
    return -1;
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    while(cin >> L >> R >> C && L && R && C)
    {
        memset(dist, -1, sizeof dist);
        for (int i = 0; i < L; i ++ )
            for (int j = 0; j < R; j ++ )
                cin >> g[i][j];
        Position st, ed;
        for (int i = 0; i < L; i ++ )
            for (int j = 0; j < R; j ++ )
                for (int k = 0; k < C; k ++ )
                {
                    // 由于POJ的古老评测机,这里必须有一个中间变量才能编译
                    Position t = {i, j, k};
                    if (g[i][j][k] == 'S') st = t;
                    if (g[i][j][k] == 'E') ed = t;
                }
        // 用一个变量存储函数返回值
        int distance = bfs(st, ed);
        // -1 表示到达不了终点,其他表示到达终点的时间
        if (distance == -1) puts("Trapped!");
        else printf("Escaped in %d minute(s).\n", distance);
    }
    // fclose(stdout);//输出结束
    return 0;
}

C.Catch That Cow(抓住那头牛)

主要知识点:BFS求最短路

题意概括:

牛和农夫都在一个数轴上,假设牛不动,而农夫有以下三种移动方式

(1)向正方向移动,X + 1

(2)向负方向移动,X - 1

(3)自身坐标翻倍,X * 2

求农夫需要移动多少次,才能到达牛的位置,抓住牛?

解题思路:

一维BFS问题,模拟,秒杀一气呵成。

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1e5 + 10;

int n, k;
int dist[2 * N]; // 这里需要二倍长度,否则倍增的时候有可能会爆空间

int bfs()
{
    queue<int> q;
    q.push(n);
    dist[n] = 0;
    while (q.size())
    {
        int t = q.front(); q.pop();
        if (t == k) return dist[k];
        // t + 1 不得超过 牛的极限坐标 
        if (t + 1 < N && dist[t + 1] == -1) 
        {
            dist[t + 1] = dist[t] + 1;
            q.push(t + 1);
        }
        // t - 1 不得小于 0
        if (t - 1 >= 0 && dist[t - 1] == -1)
        {
            dist[t - 1] = dist[t] + 1;
            q.push(t - 1);
        }
        // t * 2 不得超过 2倍 牛的极限坐标
        if (t * 2 < 2 * N && dist[2 * t] == -1)
        {
            dist[2 * t] = dist[t] + 1;
            q.push(2 * t);
        }
    }
    return -1;
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    cin >> n >> k;
    memset(dist, -1, sizeof dist);
    cout << bfs();
    // fclose(stdout);//输出结束
    return 0;
}

D.Fliptile(枚举)

主要知识点:状态压缩,枚举,递推,模拟

题意概括:

给定一个 M × N M \times N M×N 的0-1矩阵,要将这个矩阵转换成完全只有0的矩阵,每次翻转会连带着翻转相邻的四个格子(如果有的话),求最小翻转数方案中字典序最小的。

解题思路:

首先排除枚举所有节点的方案, 2 15 × 15 2 ^ {15 \times 15} 215×15 必定超时,这是一道有点思维难度的题,我们必须具有递推思想,要想明白:在第一行的方案确定的情况下,为了保证上一行能够全为0,后面的所有行都可以由前一行的状态递推出来,也就是说,我们唯一需要枚举的,仅仅是第一行。

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 15 + 10;

int m, n;   
// st为每个方格的状态, backup用于记录初始状态,方便回到初始状态
int st[N][N], backup[N][N];	
// way代表当前操作的具体矩阵,last代表最终结果的操作矩阵
int way[N][N], last[N][N];
// ans记录最小操作个数
int ans = 0x3f3f3f3f;
// 翻转操作
void turn(int x, int y)
{
    way[x][y] = 1;	// 记录该操作
    // 一系列翻转操作
    if (x - 1 > 0) st[x - 1][y] = !st[x - 1][y];
    if (x + 1 <= m) st[x + 1][y] = !st[x + 1][y];
    st[x][y] = !st[x][y];
    if (y - 1 > 0) st[x][y - 1] = !st[x][y - 1];
    if (y + 1 <= n) st[x][y + 1] = !st[x][y + 1];
}
// 进行第一行的操作
int count (int state)
{
    int cnt = 0;	// cnt记录操作个数
    for (int i = 1; i <= n; i ++ )
    {
        if (state & (1 << i - 1)) 
        {
            // 字典序从小到大的顺序,i应该从右往左进行操作
            turn(1, n - i + 1);
            cnt ++;
        }
    }
    return cnt;
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    cin >> m >> n;
    // 输入初始状态
    for (int i = 1; i <= m; i ++ )
        for (int j = 1; j <= n; j ++ )
            cin >> st[i][j];
    // 将初始状态做备份
    memcpy(backup, st, sizeof st);
    // 枚举的是第一行的操作(状态压缩,字典序从小到大)
    for (int k = 0; k < 1 << n; k ++ ) 
    {
        // 每次回到初始状态
        memcpy(st, backup, sizeof st);
        memset(way, 0, sizeof way);
        // count函数:进行第一行的操作
        int t = count(k);
        // 由第一行的状态,递推之后m - 1行的状态
        for (int i = 2; i <= m; i ++ )
        {
            for (int j = 1; j <= n; j ++ )
            {
                // 如果前一行的这一列的状态为1
                // 那么必须在当前一行的这一列进行一次翻转
                // 从而保证上一行全部为0
                if (st[i - 1][j])	
                {
                    turn(i, j); 
                    t ++;
                }
            }
        }
        int ok = 1;	// 用于标记是否成功将最后一行也变为0
        for (int i = 1; i <= n; i ++ )
            if (st[m][i])	// 只要最后一行有状态1存在
                ok = 0;		// 就标记为不成功
        if (ok)
            // 更新最小解,因为第一行是按字典序从小到大枚举的
            // 所以操作次数相同,第一次更新的就是最小字典序解
            if (t < ans)	
            {
                ans = t;
                memcpy(last, way, sizeof way);
            }
    }
    // 找不到可行解
    if (ans == 0x3f3f3f3f) puts("IMPOSSIBLE");
    else
    {
        // cout << ans << endl;
        for (int i = 1; i <= m; i ++ )
        {
            for (int j = 1; j <= n; j ++ )
                cout << last[i][j] << ' ';
            cout << endl;
        }
    }
    // fclose(stdout);//输出结束
    return 0;
}

E.Find The Multiple(找倍数)

主要知识点:BFS,完全二叉树,枚举

题意概括:

给定 1 — — 200 1——200 1200 的整数 n n n ,求 n n n 的倍数,这个倍数需要满足,在十进制表示中,仅由1和0两个数字组成,这个倍数在十进制上不超过100位。

解题思路:

这道题最基本的思路是BFS一位数一位数地拓展,直到找到能够整除的方案。但是 i n t int int 类型的数组不能存储足够大的数, l o n g long long l o n g long long 类型也是如此,只有 u n s i g n e d unsigned unsigned l o n g long long l o n g long long 才能勉强存下。

我们可以用同余进行优化,只存储每种方案的余数,这样我们就可以使用 i n t int int ,从而可以扩展更多方案。

这是一道数据很模糊的题,复杂度分析很困难,多项输入,但不知道项数,倍数的100位也完全是干扰项,最终测出的数据,最大为 2 19 = 524288 2^{19} = 524288 219=524288 ,但为了求稳,还是选择了将数组开到足够大 。

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1e8 + 10;

int n;
int mod[N];     // mod[i]代表的是第i个数 MOD n的余数
                // 第i个数是指完全二叉树的第i个节点
                // 通过构建完全二叉树来枚举

int bfs()
{
    queue<int> q;
    q.push(1);
    while(q.size())
    {
        int t = q.front(); q.pop();
        if (mod[t] == 0) return t;
        // 拓展左孩子
        if (2 * t < N)
        {
            mod[2 * t] = mod[t] * 10 % n;
            q.push(2 * t);
        }
        // 拓展右孩子
        if (2 * t + 1 < N)
        {
            mod[2 * t + 1] = (mod[t] * 10 + 1) % n;
            q.push(2 * t + 1);
        }
    }
    return 0;
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    while (cin >> n && n)
    {
        mod[1] = 1 % n;
        // 利用BFS,构建完全二叉树,每一层代表一位数字,最高位为1
        // 当枚举到mod == 0的情况停止
        int i = bfs(); // 提取mod == 0的那一个节点
        // 知道这个节点编号就可以求出他所有的祖先编号
        vector<int> ans;
        while(i)
        {
            ans.push_back(i % 2);
            i /= 2;     // 访问父亲节点
        }
        reverse(ans.begin(), ans.end());
        for (i = 0; i < ans.size(); i ++ ) cout << ans[i];
        cout << endl;
    }
    // fclose(stdout);//输出结束
    return 0;
}

F.Prime Path(质数路径)

题意概括:

给定两个四位数的质数 a a a b b b a a a 为初状态, b b b 为末状态,要想从初状态变成末状态,可以进行以下操作:

选择当前质数,可以通过更换某一位的数字,来将这个质数变成另一个四位质数。

求最少需要多少次操作,才能将初状态变成末状态。

解题思路:

BFS问题,将每个四位质数作为一个状态,并通过条件判断能否转移,

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1e4 + 10;

int a, b;
bool st[N];
int dist[N];	// 距离数组,记录需要几步到这个状态(质数)
int primes[N], p[N];
int cnt = 0, num = 0;

// 求出所有的四位质数(质数筛)
void init()
{
    for (int i = 2; i < N; i ++ )
    {
        if (!st[i])
        {
            primes[cnt ++] = i;
            if (i >= 1000 && i <= 9999) p[num ++] = i;
        }
        for (int j = 0; primes[j] <= N / i; j ++ )
        {
            st[primes[j] * i] = 1;
            if (i % primes[j] == 0) break;
        }
    }
}
// 判断是否能够转移
bool ok(int x, int y)
{
    int ans = 0;
    while (x)
    {
        int t1 = x % 10;
        int t2 = y % 10;
        x /= 10, y /= 10;
        if (t1 != t2) ans ++;
    }
    return ans == 1;
}

void solve()
{
    // 记得初始化距离数组
    memset(dist, -1, sizeof dist);
    cin >> a >> b;
    queue<int> q;
    while(q.size()) q.pop();
    q.push(a);
    dist[a] = 0;
    while(q.size())
    {
        int t = q.front(); q.pop();
        if (t == b) 
        {
            cout << dist[b] << endl;
            return;
        }
        // 枚举所有状态(四位质数)
        for (int j = 0; j < num; j ++ )	
        {
            // 判断能否转移 以及 是否到达过该状态
            if (ok(p[j], t) && dist[p[j]] == -1)
            {
                dist[p[j]] = dist[t] + 1;
                q.push(p[j]);
            }
        }
    }
    if (dist[b] == -1) cout << "Impossible\n";
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    int t;
    cin >> t;
    init();
    while (t --)
    {
        solve();
    }
    // fclose(stdout);//输出结束
    return 0;
}

G.Shuffle’m Up(洗牌)

主要知识点:BFS求最短路

题意概括:

给定两个字符串s1,s2,对其进行洗牌操作,即以下方式进行重组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9lmtCeRJ-1653023066918)(C:\Users\86133\AppData\Roaming\Typora\typora-user-images\image-20220520083415986.png)]

并将重组后的数组对半分,前半为s1,后半为s2,即可继续进行洗牌操作。

求最少需要多少次洗牌,才能将初状态变成末状态。

解题思路:

BFS问题,将s1+s2(合并)的长字符串作为状态,用洗牌操作更新状态,直到出现重复状态或目标状态

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>

using namespace std;

const int N = 1e5 + 10;

int t;
int c;
string s1, s2, s;
map<string, bool> st;	// 记录状态是否出现过

int bfs()
{
    st.clear();
    string state = s1; string end = s;
    st[state] = 1;
    int ans = 0;
    while (state != end)	// 洗牌,直到出现目标状态
    {
        string cur = "";	
        for (int i = 0; i < c; i ++ ) 
        {
            // 先加上s2的牌
            cur += state[i + c];
            // 再加上s1的牌
            cur += state[i];
        }
        if (st[cur]) return -1;		// 出现重复状态,不可能达到目标状态
        st[cur] = 1;
        ans ++;
        state = cur;
    }
    return ans;
}

void solve()
{
    cin >> c;
    cin >> s1 >> s2 >> s;
    s1 += s2;	// 将s1,s2合并成为一个字符串作为状态
    cout << t << ' ' << bfs() << endl;
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    int cnt;
    cin >> cnt;
    for (t = 1; t <= cnt; t ++ )
    {
        solve();
    }
    // fclose(stdout);//输出结束
    return 0;
}

H.Pots(水罐)

主要知识点:BFS求最短路

题意概括:

有两个水罐,可以进行以下三类操作(六种):

(1) 装满某个水罐(1号 或 2号)

(2) 清空某个水罐(1号 或 2号)

(3) 将某个水罐的水倒到另一个水罐(直到其中一个水罐满 或者 空)

状态设置是两个水罐中的水。

求最少需要多少次操作,才能将初状态变成末状态。

解题思路:

BFS问题,将两个水罐的水设为状态,枚举所有可能的操作,并记录前移状态以及其操作种类,通过末状态进行反向递推得到操作路径。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;

typedef pair<int, int> State;	// 用一对整数来代表状态

const int N = 1e5 + 10;

int a, b, c;

struct Ways		// 操作方式
{
    int op, a, b;	// 操作种类,操作对象
    State st;		// 前一个状态
};
map<State, Ways> pre;	// 记录一个状态的前一个,反向递推出路径
State ed;

int bfs()
{
    State t;
    map<State, int> dist;	// 初状态到这个状态所需的步数
    for (int i = 0; i <= a; i ++ )
    for (int j = 0; j <= b; j ++ )
    {
        t = {i, j};
        dist[t] = -1;
    }
    queue<State> q;
    t = {0, 0};
    q.push(t);
    dist[t] = 0;
    pre[t] = {0, 0, 0, t};
    while(q.size())
    {
        State t = q.front(); q.pop();
        // 达到目标状态时,返回步数
        if (t.x == c || t.y == c) 
        {
            ed = t;		// 记录最后状态,方便反推
            return dist[t];
        }
        State next;
        // 操作1,将第一个水罐装满
        if (t.x < a)
        {
            next = {a, t.y};
            if (dist[next] == -1) 
            {
                q.push(next);
                dist[next] = dist[t] + 1;
                pre[next] = {1, 1, 0, t};
            }
        }
        // 操作2,将第二个水罐装满
        if (t.y < b)
        {
            next = {t.x, b};
            if (dist[next] == -1) 
            {
                q.push(next);
                dist[next] = dist[t] + 1;
                pre[next] = {1, 2, 0, t};
            }
        }
        // 操作3,将第一个水罐清空
        if (t.x > 0)
        {
            next = {0, t.y};
            if (dist[next] == -1) 
            {
                q.push(next);
                dist[next] = dist[t] + 1;
                pre[next] = {2, 1, 0, t};
            }
        }
        // 操作4,将第二个水罐清空
        if (t.y > 0)
        {
            next = {t.x, 0};
            if (dist[next] == -1) 
            {
                q.push(next);
                dist[next] = dist[t] + 1;
                pre[next] = {2, 2, 0, t};
            }
        }
        // 操作5,将第一个水罐的水倒到第二个水罐
        if (t.x > 0 && t.y < b)
        {
            int delta = min(b - t.y, t.x);
            next = {t.x - delta, t.y + delta};
            if (dist[next] == -1) 
            {
                q.push(next);
                dist[next] = dist[t] + 1;
                pre[next] = {3, 1, 2, t};
            }
        }
        // 操作6,将第二个水罐的水倒到第一个水罐
        if (t.y > 0 && t.x < a)
        {
            int delta = min(a - t.x, t.y);
            next = {t.x + delta, t.y - delta};
            if (dist[next] == -1) 
            {
                q.push(next);
                dist[next] = dist[t] + 1;
                pre[next] = {3, 2, 1, t};
            }
        }
    }
    return -1;
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    cin >> a >> b >> c;
    int n = bfs();		// n为达到目标状态的操作数
    if (n == -1) 
    {
        puts("impossible");
        return 0;
    }
    cout << n << endl;
    vector<Ways> v;
    for (int i = 0; i < n; i ++ )
    {
        v.push_back(pre[ed]);	// 递推n个操作
        ed = pre[ed].st;		// 并得到前一个状态
    }
    // 反向输出,才是正确的顺序,因为我们是从末状态反向推出来的操作
    for (int i = n - 1; i >= 0; i -- )
    {
        Ways path = v[i];
        int op, x, y;
        op = path.op;
        x = path.a;
        y = path.b;
        if (op == 1)
        {
            printf("FILL(%d)\n", x);
        }else if (op == 2)
        {
            printf("DROP(%d)\n", x);
        }else 
            printf("POUR(%d,%d)\n", x, y);
    }
    // fclose(stdout);//输出结束
    return 0;
}

I.Fire Game(玩火)(本题提交与ACwing评测机)

主要知识点:BFS求最短路

题意概括:

两个小孩在玩火,只有草丛 “#” 才能烧起来,两个小孩会在同时点燃两个草丛(也可能是同一个),求把地图上所有草丛都烧着的最短时间。

解题思路:

BFS问题,首先需要判断连通块个数,如果连通块大于2,则两个小孩不能通过一次点火点燃所有草丛;如果连通块等于2,则两个小孩需要分开点燃两堆草丛;如果连通块等于1,则两个小孩可以在这一堆里面任选 1~2 个草丛点燃。

点燃之后,计算所有草丛被点燃时的时间dist,最大的时间就是所有草丛都烧着的时间,枚举所有方案,并记录其中的最小值。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;

typedef pair<int, int> PII;

const int N = 100 + 10;

int n, m;
char g[N][N];
int dist[N][N];		// 当前草丛烧起来的所花的最短时间(顺带记录是否烧起来过)
vector<PII> p[N];	// 记录连通块中的节点
int cnt;			// 连通块(草丛)的个数cnt

int bfs(int flag, int x1, int y1, int x2, int y2)
{
    int mx = 0;
    // 判断连通块时千万不能每次都更新dist(记录是否访问)!
    if (flag > 1) memset(dist, -1, sizeof dist);
    int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
    queue<PII> q;
    q.push({x1, y1});	
    dist[x1][y1] = 0;
    if (flag == 1 || (x1 == x2 && y1 == y2));
    else 
    {
        q.push({x2, y2});	// 有时需要两个火源进行BFS
        dist[x2][y2] = 0;
    }
    while (q.size())
    {
        PII t = q.front(); q.pop();
        int x = t.x, y = t.y;
        if (flag == 1) 
        {
            p[cnt].push_back(t);
        }
        mx = max(mx, dist[x][y]);
        for (int i = 0; i < 4; i ++ )
        {
            int a = x + dx[i], b = y + dy[i];
            PII next = {a, b};
            if (a <= 0 || a > n || b <= 0 || b > m) continue;
            if (g[a][b] == '#' && dist[a][b] == -1)
            {
                q.push(next);
                dist[a][b] = dist[x][y] + 1;
            }
        }
    }
    // cout << mx << endl;
    return mx;
}
// 判断连通块
void check()
{
    cnt = 0;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            if (g[i][j] == '#' && dist[i][j] == -1) 
            {
                cnt ++;
                if (cnt > 2) return;
                bfs(1, i, j, 0, 0);		// 对当前连通块进行标记
            }
}
// 枚举两个连通块的情况
int both()
{
    int mn = 1e9;
    // 两个小孩必须分别在两个连通块中放火
    for (int i = 0; i < p[1].size(); i ++ )
        for (int j = 0; j < p[2].size(); j ++ )
        {
            PII t1 = p[1][i], t2 = p[2][j];
            mn = min(mn, bfs(2, t1.x, t1.y, t2.x, t2.y));
        }
    return mn;
}
// 枚举一个连通块的情况
int only()
{
    int mn = 1e9;
    // 两个小孩在同一个连通块中放火,可以是同个点
    for (int i = 0; i < p[1].size(); i ++ )
        for (int j = 0; j < p[1].size(); j ++ )
        {
            PII t1 = p[1][i], t2 = p[1][j];
            mn = min(mn, bfs(3, t1.x, t1.y, t2.x, t2.y));
        }
    return mn;
}

int solve()
{
    memset(dist, -1, sizeof dist);
    p[1].clear();
    p[2].clear();
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) cin >> g[i] + 1;
    check();	// 判断连通块(草丛)的个数cnt
    int ans;
    if (cnt > 2) ans = -1;
    else if (cnt == 2) ans = both();
    else if (cnt) ans = only();
    else ans = -1;
    return ans;
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    int t;
    cin >> t;
    for (int i = 1; i <= t; i ++ )
    {
        int ans = solve();
        printf("Case %d: %d\n", i, ans);
    }
    // fclose(stdout);//输出结束
    return 0;
}

I.Fire!(火灾)

主要知识点:BFS求最短路

题意概括:

在一个迷宫里,有复数个火源 和 一位逃生者,逃生者和火源都会以1格每单位时间的速度移动,逃生者需要从迷宫边缘逃生,求最短时间。

解题思路:

BFS问题,先对所有火源进行多源BFS,求出每个点被火焰燃烧的时间点,然后让逃生者进行模拟逃生(BFS),每个点,只有在火焰燃烧到之前到达,才能更新到达时间,否则就无法到达。

当BFS拓展到任何一个边界时,下一步就可以直接走出迷宫。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;

typedef pair<int, int> PII;

const int N = 1000 + 10;

int n, m;
char g[N][N];
// dist是火焰到达的时间点, d是逃生者到达的时间点
int dist[N][N], d[N][N];		
int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
// 火源先拓展
void bfs1()
{
    // 这里必须用一个较大的值来表示这个点火焰没有到达
    memset(dist, 0x3f, sizeof dist);
    queue<PII> q1;
    while (q1.size()) q1.pop();
    // 导入所有火源
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            if (g[i][j] == 'F')
            {
                PII t = {i, j};
                q1.push(t);
                dist[i][j] = 0;
            }
   	// 正常的BFS拓展
    while (q1.size())    
    {
        PII t = q1.front(); q1.pop();
        int x = t.x, y= t.y;
        for (int i = 0; i < 4; i ++ )
        {
            int a = x + dx[i], b = y + dy[i];
            if (g[a][b] == '#') continue;
            if (a <= 0 || a > n || b <= 0 || b > m) continue;
            if (dist[a][b] == 0x3f3f3f3f)
            {
                dist[a][b] = dist[x][y] + 1;
                PII next = {a, b};
                q1.push(next);
            } 
        }
    }
}
// 逃生者模拟逃生
int bfs2()
{
    // 这里就不需要用大数标记,用-1表示是否到达过就行了。
    memset(d, -1, sizeof d);
    queue<PII> q;
    while (q.size()) q.pop();
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            if (g[i][j] == 'J')
            {
                PII t = {i, j};
                q.push(t);
                d[i][j] = 0;
            }   
    while (q.size())
    {
        PII t = q.front(); q.pop();
        int x = t.x, y = t.y;
        // 第一次到达边界,答案就是加一步出迷宫
        if (x == 1 || x == n || y == 1 || y == m) return d[x][y] + 1;
        for (int i = 0; i < 4; i ++ )
        {
            int a = x + dx[i], b = y + dy[i];
            if (g[a][b] == '#') continue;
            if (a <= 0 || a > n || b <= 0 || b > m) continue;
            // 只有dist大于d,才能进行拓展(这就是为什么要用大数标记dist)
            if (d[x][y] + 1 < dist[a][b] && d[a][b] == -1)
            {
                PII next = {a, b};
                q.push(next);
                d[a][b] = d[x][y] + 1;
            }
        }   
    }    
    return -1;
}

void solve()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> g[i] + 1; 

    bfs1();
    int ans = bfs2();
    if (ans == -1) puts("IMPOSSIBLE");
    else cout << ans << endl;
    // for (int i = 1; i <= n; i ++ )  
    // {
    //     for (int j = 1; j <= n; j ++ )
    //         cout << dist[i][j] << ' ';
    //     cout << endl;
    // }
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    int t;
    cin >> t;
    for (int i = 1; i <= t; i ++ )
    {
        solve();
    }
    // fclose(stdout);//输出结束
    return 0;
}

J.迷宫问题

主要知识点:BFS求最短路

题意概括:

给定一个 $5 \times 5 $ 的迷宫矩阵,求左上角 ( 0 , 0 ) (0,0) (0,0) 到 右下角 ( 4 , 4 ) (4,4) (4,4) 的最短路径

注意,输出所有走过的点,即为路径

解题思路:

BFS问题,只不过需要记录路径。

在水罐那道题我们已经见识了反推路径,这道题我们来用个新的方法,从终点开始走到起点,这样直接得到的就是正向路径了。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;

typedef pair<int, int> PII;

const int N = 10 + 10;

int g[N][N];
int st[N][N];		// 用一个数组记录是否重复走过就行了
map<PII, PII> pre;	// 记录前一个节点

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

void bfs(PII ed)
{
    memset(st, 0, sizeof st);
    queue<PII> q;
    q.push(ed);
    st[ed.x][ed.y] = 1;
    while(q.size())
    {
        PII t = q.front(); q.pop();
        int x = t.x, y = t.y;
        if (x == 0 && y == 0) return;
        for (int i = 0; i < 4; i ++ )
        {
            int a = x + dx[i], b = y + dy[i];
            PII next;
            next = {a, b};
            if (a < 0 || b < 0 || a >= 5 || b >= 5) continue;
            if (g[a][b] == 1) continue;
            if (st[a][b] == 1) continue;
            q.push(next);
            pre[next] = t;
            st[a][b] = 1;
        }
    }
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    for (int i = 0; i < 5; i ++ )
        for (int j = 0; j < 5; j ++ )
            cin >> g[i][j];
    PII ed;
    ed = {4, 4};
    bfs(ed);
    PII state;
    state = {0, 0};
    while(state.x != 4 || state.y != 4)
    {
        // 得到节点,直接输出
        printf("(%d, %d)\n", state.x, state.y);
        state = pre[state];
    }
    // 最后一个节点手动输出
    printf("(%d, %d)\n", state.x, state.y);
    // fclose(stdout);//输出结束
    return 0;
}

K.Oil Deposits(石油储量)

主要知识点:BFS求最短路

题意概括:

可以认为,在以某个石油的九宫格内的石油,都是同一批石油,否则则认为不是同一批;给定一个地图,求地图上有多少批不同的石油。

解题思路:

BFS判断连通块问题,判断有多少个连通块即可,连通块的注释可以看下"玩火"那道题

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;

typedef pair<int, int> PII;

const int N = 100 + 10;

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

int cnt;
void bfs(int i, int j)
{
    queue<PII> q;
    q.push({i, j});
    st[i][j] = cnt;
    while (q.size())
    {
        PII t = q.front(); q.pop();
        int x = t.x, y = t.y;
        for (int i = -1; i <= 1; i ++ )
            for (int j = -1; j <= 1; j ++ )
            {
                int a = x + i, b = y + j;
                if (a <= 0 || a > n || b <= 0 || b > m) continue;
                if (st[a][b]) continue;
                if (g[a][b] != '@') continue;
                PII next = {a, b};
                q.push(next);
                st[a][b] = 1;
            }
    }
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    while (cin >> n >> m && n)
    {
        memset(st, 0, sizeof st);
        cnt = 0;
        for (int i = 1; i <= n; i ++ ) cin >> g[i] + 1;
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= m; j ++ )
            {
                if (g[i][j] == '@' && st[i][j] == 0) 
                {
                    cnt ++;
                    bfs(i, j);
                }
            }
        cout << cnt << endl;
    }
    
    // fclose(stdout);//输出结束
    return 0;
}

L.非常可乐

主要知识点:BFS求最短路

题意概括:

一瓶可乐的容量为s,有两个空杯子容量分别为n,m;

三个杯/瓶可以相互倒,每次倒可乐算一步,求恰好平分可乐的最小步数。

解题思路:

类似于水罐那道题,只不过这次我们需要自己分析操作的种类。

将三个杯/瓶分别称为 a, b, c。

(1)a -> b

(2)a -> c

(3)b -> a

(4)b -> c

(5)c -> a

(6)c -> b

倒水的方式跟水罐那道题相互倒水几乎一样。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;

typedef pair<int, int> PII;
typedef pair<int, pair<int, int>> PIII;
const int N = 100 + 10;

int s, n, m;

int dist[N][N][N];	// 记录到某个状态的最短步数

int bfs()
{
    memset(dist, -1, sizeof dist);
    queue<PIII> q;
    PIII start = {s, {0, 0}};	// 这里可以用结构体定义,可读性会强点
    q.push(start);
    dist[s][0][0] = 0;
    while (q.size())
    {
        PIII t = q.front(); q.pop();
        int a = t.x, b = t.y.x, c = t.y.y;
        // 终止状态
        // 如果其中一杯已经满足有一半的可乐了,那么另一半一定在另外两杯中
        if (a == s / 2 || c == s / 2 || b == s / 2) 
        {
            int ans = dist[a][b][c];
            // 如果没有一个为空(则另外一个也是一半)
            // 则需要再多加一步将两个合并为另一半可乐
            if (a && b && c) ans ++;
            return ans;
        }
        if (a < s)
        {
        	// (1)a -> b
            if (b > 0)
            {
                int da = min(s - a, b);
                PIII next = {a + da, {b - da, c}};
                if (dist[a + da][b - da][c] == -1) 
                {
                    dist[a + da][b - da][c] = dist[a][b][c] + 1;
                    q.push(next);
                }
            }

            // (2)a -> c
            if (c > 0)
            {
                int da = min(s - a, c);
                PIII next = {a + da, {b, c - da}};
                if (dist[a + da][b][c - da] == -1)
                {
                    dist[a + da][b][c - da] = dist[a][b][c] + 1;
                    q.push(next);
                }
            }
        }
        if (b < n)
        {

            // (3)b -> a
            if (a > 0)
            {
                int db = min(n - b, a);
                PIII next = {a - db, {b + db, c}};
                if (dist[a - db][b + db][c] == -1)
                {
                    dist[a - db][b + db][c] = dist[a][b][c] + 1;
                    q.push(next);
                }
            }

            // (4)b -> c
            if (c > 0)
            {
                int db = min(n - b, c);
                PIII next = {a, {b + db, c - db}};
                if (dist[a][b + db][c - db] == -1)
                {
                    dist[a][b + db][c - db] = dist[a][b][c] + 1;
                    q.push(next);
                }
            }
        }
        if (c < m)
        {

            // (5)c -> a
            if (a > 0)
            {
                int dc = min(m - c, a);
                PIII next = {a - dc, {b, c + dc}};
                if (dist[a - dc][b][c + dc] == -1) 
                {
                    dist[a - dc][b][c + dc] = dist[a][b][c] + 1;
                    q.push(next);
                }
            }

            // (6)c -> b
            if (b > 0)
            {
                int dc = min(m - c, b);
                PIII next = {a, {b - dc, c + dc}};
                if (dist[a][b - dc][c + dc] == -1) 
                {
                    dist[a][b - dc][c + dc] = dist[a][b][c] + 1;
                    q.push(next);
                }
            }
        }
    }
    return -1;
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    while (cin >> s >> n >> m && s && n && m)
    {
        if (s % 2)
        {
            cout << "NO\n";
            continue;
        }
        int ans = bfs();
        if (ans == -1) puts("NO");
        else cout << ans << endl;
    }
    
    // fclose(stdout);//输出结束
    return 0;
}

M.Find a way(找"去KFC的"路)

主要知识点:BFS求最短路

题意概括:

地图上标记了两个人和若干个KFC的位置。

这两个人想去同一个KFC,求两个人花的总时间最少为多少。

解题思路:

BFS问题,两次BFS分别预处理两个人到所有KFC的距离,最后求距离之和最小的即可

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#define x first
#define y second
using namespace std;

typedef pair<int, int> PII;
typedef pair<int, pair<int, int>> PIII;
const int N = 200 + 10;

int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
int n, m;
char g[N][N];
// dis:第一个人到地图上点的最短距离
// d:第二个人到地图上点的最短距离
int dis[N][N], d[N][N];		
PII a, b;	// 记录两个人的坐标
// 预处理第一个人到地图上点的最短距离
void bfs1()
{
    // 求最短距离的时候最好还是用大数标记,否则有的KFC走不到就麻烦了
    memset(dis, 0x3f, sizeof dis);
    queue<PII> q1;
    q1.push(a);
    dis[a.x][a.y] = 0;
    while (q1.size())
    {
        PII t = q1.front(); q1.pop();
        int x = t.x , y = t.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;
            if (dis[a][b] == 0x3f3f3f3f) 
            {
                dis[a][b] = dis[x][y] + 1;
                q1.push({a, b});
            }
        }
    }
}
// 预处理第二个人到地图上点的最短距离
void bfs2()
{
    memset(d, 0x3f, sizeof d);
    queue<PII> q;
    q.push(b);
    d[b.x][b.y] = 0;
    while (q.size())
    {
        PII t = q.front(); q.pop();
        int x = t.x , y = t.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;
            if (d[a][b] == 0x3f3f3f3f) 
            {
                d[a][b] = d[x][y] + 1;
                q.push({a, b});
            }
        }
    }
}

void solve()
{
    vector<PII> ans;	// ans 记录KFC的位置
    ans.clear();
    for (int i = 1; i <= n; i ++ ) cin >> g[i] + 1;
    for (int i = 1; i <= n; i ++ )
        for (int j  = 1; j <= m; j ++ )
        {
            if (g[i][j] == 'Y') a = {i, j};
            if (g[i][j] == 'M') b = {i, j};
            if (g[i][j] == '@') ans.push_back({i, j});
        }
    bfs1();
    bfs2();
    int mn = 1e9;
    // 分别求所有
    for (int i = 0; i < ans.size(); i ++ )
    {
		int x = ans[i].x, y = ans[i].y;
		mn = min(mn, dis[x][y] + d[x][y]);
    }
    cout << mn * 11 << endl;
}

int main()
{
    // freopen("data.in","r",stdin);  // 文件输入
    // freopen("data.out","w",stdout);// 文件输出
    while (cin >> n >> m)
    {
        solve();
    }
    // fclose(stdout);//输出结束
    return 0;
}
  • 20
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
1 字符串处理 5 1.1 KMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2 e-KMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.3 Manacher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.4 AC 自动机 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1.5 后缀数组 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.5.1 DA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.5.2 DC3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 1.6 后缀自动机 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 1.6.1 基本函数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 1.6.2 例题 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 1.7 字符串 hash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2 数学 25 2.1 素数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 2.1.1 素数筛选(判断 <MAXN 的数是否素数) . . . . . . . . . . . . . . . . 25 2.1.2 素数筛选(筛选出小于等于 MAXN 的素数) . . . . . . . . . . . . . . . 25 2.1.3 大区间素数筛选(POJ 2689) . . . . . . . . . . . . . . . . . . . . . . . 25 2.2 素数筛选和合数分解 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 2.3 扩展欧几里得算法(求 ax+by=gcd 的解以及逆元) . . . . . . . . . . . . . . . 27 2.4 求逆元 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 2.4.1 扩展欧几里德法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 2.4.2 简洁写法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 2.4.3 利用欧拉函数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 2.5 模线性方程组 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 2.6 随机素数测试和大数分解 (POJ 1811) . . . . . . . . . . . . . . . . . . . . . . . 29 2.7 欧拉函数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 2.7.1 分解质因素求欧拉函数 . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 2.7.2 筛法欧拉函数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 2.7.3 求单个数的欧拉函数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 2.7.4 线性筛(同时得到欧拉函数和素数表) . . . . . . . . . . . . . . . . . . 32 2.8 高斯消元(浮点数) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 2.9 FFT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 2.10 高斯消元法求方程组的解 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 2.10.1 一类开关问题,对 2 取模的 01 方程组 . . . . . . . . . . . . . . . . . . . 37 2.10.2 解同余方程组 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 2.11 整数拆分 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 2.12 求 A B 的约数之和对 MOD 取模 . . . . . . . . . . . . . . . . . . . . . . . . . . 43 2.13 莫比乌斯反演 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 2.13.1 莫比乌斯函数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 2.13.2 例题:BZOJ2301 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 2.14 Baby-Step Giant-Step . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 2.15 自适应 simpson 积分 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 2.16 斐波那契数列取模循环节 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 2.17 原根 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 2.18 快速数论变换 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 2.18.1 HDU4656 卷积取模 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 2.19 其它公式 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 2.19.1 Polya . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 kuangbin 1 ACM Template of kuangbin 3 数据结构 56 3.1 划分树 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 3.2 RMQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 3.2.1 一维 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 3.2.2 二维 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 3.3 树链剖分 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 3.3.1 点权 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 3.3.2 边权 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 3.4 伸展树(splay tree) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 3.4.1 例题:HDU1890 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 3.4.2 例题:HDU3726 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 3.5 动态树 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 3.5.1 SPOJQTREE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 3.5.2 SPOJQTREE2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 3.5.3 SPOJQTREE4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 3.5.4 SPOJQTREE5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 3.5.5 SPOJQTREE6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 3.5.6 SPOJQTREE7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 3.5.7 HDU4010 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 3.6 主席树 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 3.6.1 查询区间多少个不同的数 . . . . . . . . . . . . . . . . . . . . . . . . . . 95 3.6.2 静态区间第 k 大 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 3.6.3 树上路径点权第 k 大 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 3.6.4 动态第 k 大 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 3.7 Treap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 3.8 KD 树 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 3.8.1 HDU4347 K 近邻 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 3.8.2 CF44G . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 3.8.3 HDU4742 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 3.9 替罪羊树 (ScapeGoat Tree) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 3.9.1 CF455D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 3.10 动态 KD 树 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 3.11 树套树 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 3.11.1 替罪羊树套 splay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 4 图论 130 4.1 最短路 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 4.1.1 Dijkstra 单源最短路 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 4.1.2 Dijkstra 算法 + 堆优化 . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 4.1.3 单源最短路 bellman_ford 算法 . . . . . . . . . . . . . . . . . . . . . . . 131 4.1.4 单源最短路 SPFA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 4.2 最小生成树 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 4.2.1 Prim 算法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 4.2.2 Kruskal 算法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 4.3 次小生成树 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 4.4 有向图的强连通分量 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 4.4.1 Tarjan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 4.4.2 Kosaraju . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 4.5 图的割点、桥和双连通分支的基本概念 . . . . . . . . . . . . . . . . . . . . . . . 138 4.6 割点与桥 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 4.6.1 模板 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 kuangbin 2 ACM Template of kuangbin 4.6.2 调用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 4.7 边双连通分支 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 4.8 点双连通分支 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 4.9 最小树形图 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 4.10 二分图匹配 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 4.10.1 邻接矩阵(匈牙利算法) . . . . . . . . . . . . . . . . . . . . . . . . . . 149 4.10.2 邻接表(匈牙利算法) . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 4.10.3 Hopcroft-Karp 算法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 4.11 二分图多重匹配 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 4.12 二分图最大权匹配(KM 算法) . . . . . . . . . . . . . . . . . . . . . . . . . . 153 4.13 一般图匹配花树 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 4.14 一般图最大加权匹配 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 4.15 生成树计数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 4.16 最大流 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 4.16.1 SAP 邻接矩阵形式 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 4.16.2 SAP 邻接矩阵形式 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 4.16.3 ISAP 邻接表形式 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 4.16.4 ISAP+bfs 初始化 + 栈优化 . . . . . . . . . . . . . . . . . . . . . . . . . 165 4.16.5 dinic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 4.16.6 最大流判断多解 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 4.17 最小费用最大流 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 4.17.1 SPFA 版费用流 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 4.17.2 zkw 费用流 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 4.18 2-SAT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 4.18.1 染色法(可以得到字典序最小的解) . . . . . . . . . . . . . . . . . . . . 172 4.18.2 强连通缩点法(拓扑排序只能得到任意解) . . . . . . . . . . . . . . . . 173 4.19 曼哈顿最小生成树 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 4.20 LCA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 4.20.1 dfs+ST 在线算法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 4.20.2 离线 Tarjan 算法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 4.20.3 LCA 倍增法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 4.21 欧拉路 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 4.21.1 有向图 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 4.21.2 无向图 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 4.21.3 混合图 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 4.22 树分治 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 4.22.1 点分治 -HDU5016 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 4.22.2 * 点分治 -HDU4918 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 4.22.3 链分治 -HDU5039 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 5 搜索 205 5.1 Dancing Links . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 5.1.1 精确覆盖 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 5.1.2 可重复覆盖 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 5.2 八数码 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 5.2.1 HDU1043 反向搜索 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 6 动态规划 212 6.1 最长上升子序列 O(nlogn) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212 6.2 背包 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212 6.3 插头 DP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 kuangbin 3 ACM Template of kuangbin 6.3.1 HDU 4285 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 7 计算几何 218 7.1 二维几何 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218 7.2 三维几何 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 7.3 平面最近点对 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242 7.4 三维凸包 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243 7.4.1 HDU4273 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243 8 其他 249 8.1 高精度 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249 8.2 完全高精度 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 8.3 strtok 和 sscanf 结合输入 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256 8.4 解决爆栈,手动加栈 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256 8.5 STL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256 8.5.1 优先队列 priority_queue . . . . . . . . . . . . . . . . . . . . . . . . . . . 256 8.5.2 set 和 multiset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257 8.6 输入输出外挂 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258 8.7 莫队算法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258 8.7.1 分块 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259 8.7.2 Manhattan MST 的 dfs 顺序求解 . . . . . . . . . . . . . . . . . . . . . . 260 8.8 VIM 配置 .
并查集是一种常用的数据结构,用于管理一个不相交集合的数据。在并查集中,每个元素都有一个父节点指向它所属的集合的代表元素。通过查找和合并操作,可以判断两个元素是否属于同一个集合,并将它们合并到同一个集合中。 在解决某些问题时,可以使用并查集进行优化。例如,在区间查询中,可以通过优化并查集的查询过程,快速找到第一个符合条件的点。 对于拆边(destroy)操作,一般的并查集无法直接实现这个功能。但是可以通过一个巧妙的方法来解决。首先,记录下所有不会被拆除的边,然后按照逆序处理这些指令。遇到拆边操作时,将该边重新加入并查集中即可。 在实现并查集时,虽然它是一种树形结构,但只需要使用数组就可以实现。可以通过将每个元素的父节点记录在数组中,来表示元素之间的关系。通过路径压缩和按秩合并等优化策略,可以提高并查集的效率。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [「kuangbin专题五并查集专题题解](https://blog.csdn.net/weixin_51216553/article/details/121643742)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [并查集(详细解释+完整C语言代码)](https://blog.csdn.net/weixin_54186646/article/details/124477838)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值