2025.3.31-4.6 学习随记(搜索)

总体来说,感觉搜索占比不会太高,主要考的就是 A_Star,IDA_Star 之类的吧。


1.Flood Fill

第一块比较简单,是比较经典的模型,可以说是 BFS 板子。

[POI 2007] GRZ-Ridges and Valleys

/*
卧槽,唐飞了。
Flood fill写成史了。
唉。
水一道绿题,意思意思。
*/
#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int n;
int g[N][N];
bool f1, f2, st[N][N];
int h, l;

void bfs(int x, int y)
{
    queue<pair<int, int> > q;
    q.push({x, y});
    st[x][y] = 1;
    while (q.size())
    {
        int tx = q.front().first, ty = q.front().second;
        q.pop();
        
        for (int i = tx - 1; i <= tx + 1; i ++ )
            for (int j = ty - 1; j <= ty + 1; j ++ )
            {
                if (i < 1 || i > n || j < 1 || j > n) continue;
                if (g[i][j] != g[tx][ty])
                {
                    if (g[i][j] > g[tx][ty]) f1 = 1;
                    else f2 = 1;
                }
                else if (!st[i][j]) st[i][j] = 1, q.push({i, j});
            }
    }
}

signed main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            scanf("%d", &g[i][j]);
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (!st[i][j]) 
            {
                f1 = 0, f2 = 0;
                bfs(i, j);
                if (!f1) h ++ ;
                if (!f2) l ++ ;
            }
            
    cout << h << ' ' << l << '\n';
    
    return 0;
}

2.最短路模型

也是模板了。

3.多源 BFS

有点难度了,开始直接把所有源点加入队列即可。

4.最小步数模型

注意的是状态的转变,更新。

5.双端队列广搜

就是开一个双端队列,手动排序。

[BalticOI 2011] Switch the Lamp On 电路维修 (Day1)

也称 01BFS。

/*
为什么不用priority_queue?
因为题目中只有0或1两个边权
所以用priority_queue会变慢
用deque可以优化掉一个O(logn)
相当于手动排序
*/
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>

using namespace std;

typedef pair<int, int> PII;

const int N = 510;

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

int bfs()//双端队列bfs
{
    memset(dis, 0x3f, sizeof dis);//答案先最大
    memset(st, 0, sizeof st);//《多 组 数 据》
    deque<PII> dq;
    dis[0][0] = 0;//从(0,0)出发
    dq.push_back({0, 0});//加入松弛队列

    int dx[] = {-1, -1, 1, 1}, dy[] = {-1, 1, 1, -1};//点坐标四方偏移量
    int ix[] = {-1, -1, 0, 0}, iy[] = {-1, 0, 0, -1};//边坐标四方偏移量
    char ch[] = "\\/\\/";//正好连住点的4个方向的点的边

    while (dq.size())
    {
        auto t = dq.front();
        dq.pop_front();//取出队头
        if (t.first == n && t.second == m) return dis[n][m];//判断终点
        if (st[t.first][t.second]) continue;//松弛过就不松弛
        st[t.first][t.second] = true;//打标记
        for (int i = 0; i < 4; i ++ )//四方向枚举
        {
            int x = t.first + dx[i], y = t.second + dy[i];//点坐标
            int a = t.first + ix[i], b = t.second + iy[i];//边坐标
            if (x < 0 || x > n || y < 0 || y > m) continue;//判断范围
            int w = 0;//默认权值为0
            if (g[a][b] != ch[i]) w = 1;//需要改边,权值变1
            if (dis[x][y] > dis[t.first][t.second] + w)//比较最短路
            {
                dis[x][y] = dis[t.first][t.second] + w;//更新
                if (w == 0) dq.push_front({x, y});//如果权值为0,加到双端队列前面
                else dq.push_back({x, y});//不然加到后面
            }
        }
    }

    // if (dis[n][m] == 0x3f3f3f3f) return -1;
    return -1;
}

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

        int t = bfs();//求解

        if (t == -1) cout << "NO SOLUTION\n";//判断结果
        else cout << t << '\n';
    }

    return 0;
}

6.双向广搜

需要开两个队列,同时搜,分别从终点、起点开始搜,然后每次更新较短的队列的同一层节点。

[NOIP 2002 提高组] 字串变换

/*
相对来说,双向BFS,01BFS和A_Star应该是BFS中最重要的了。
这题是双向BFS。
简单总结一下,就是你开两个队列做,一个从起点搜,
一个从终点搜,然后每次更新是更新一层,并且更新长度小的队列就行了的说。
比较简单。
感觉搜索在NOIP或者说CSP的占比应该不会很大。
*/
#include <bits/stdc++.h>
#define um unordered_map<string, int>
#define qu queue<string>

using namespace std;

const int N = 20;

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

int solve(qu &q, um &da, um &db, string a[], string b[], int d)
{
    while (q.size() && da[q.front()] == d)
    {
        string t = q.front(); q.pop();
        
        for (int i = 0; i < t.size(); i ++ )
            for (int j = 1; j <= n; j ++ )
                if (t.substr(i, a[j].size()) == a[j])
                {
                    string nw = t.substr(0, i) + b[j] + t.substr(i + a[j].size());
                    if (db.count(nw)) return da[t] + db[nw] + 1;
                    if (da.count(nw)) continue;
                    da[nw] = da[t] + 1;
                    q.push(nw);
                }
    }
    
    return 127;
}

int bfs(string S, string T)
{
    unordered_map<string, int> da, db;
    queue<string> qa, qb;
    qa.push(S), qb.push(T);
    da[S] = 0, db[T] = 0;
    int step = 0;
    while (qa.size() && qb.size())
    {
        int tmp;
        if (qa.size() < qb.size()) tmp = solve(qa, da, db, a, b, da[qa.front()]);
        else tmp = solve(qb, db, da, b, a, db[qb.front()]);
        
        if (tmp <= 10) return tmp;
        step ++ ;
        if (step >= 10) return 127;
    }
    
    return 127;
}

signed main()
{
    cin >> A >> B;
    while (cin >> a[n] >> b[n]) n ++ ;
    n -- ;
    
    int res = bfs(A, B);
    
    if (A == B) cout << "0\n", exit(0);
    if (res > 10) printf("NO ANSWER!\n");
    else cout << res << '\n';
    
    return 0;
}

7.A*

重点在于估价函数的设计,然后按估价函数为关键字做,第一次弹出终点即为最短路。

模板第 K 短路(弱化版)

/*
A_Star 比较典。
估价函数为最短路,但是我们需要知道一个东西,就是说A_Star第k次出队是k小路。
这个不会证,无所谓。
然后A_Star最重要的就是估计函数的设计。
其他跟普通BFS差不多。
*/
#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int n, m, cnt[N];
int d[N], st[N];
vector<pair<int, int> > g[N], rg[N];
int S, T, K;
struct QQ
{
    int pt, f, dis;
    bool operator < (const QQ &W) const
    {
        return f > W.f;
    }
};

void dijkstra()
{
    memset(d, 0x3f, sizeof d);
    priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
    q.push({0, T});
    d[T] = 0;
    
    while (q.size())
    {
        int t = q.top().second; q.pop();
        if (st[t]) continue;
        st[t] = 1;
        
        for (auto [v, w] : rg[t])
        {
            if (d[v] > d[t] + w)
            {
                d[v] = d[t] + w;
                q.push({d[v], v});
            }
        }
    }
}

int a_star()
{
    priority_queue<QQ> q;
    q.push({S, d[S], 0});
    while (q.size())
    {
        auto [u, f, dist] = q.top(); q.pop();
        
        cnt[u] ++ ;
        if (u == T && cnt[u] == K) return dist;
        
        for (auto [v, w] : g[u])
            if (cnt[v] < K) 
                q.push({v, d[v] + dist + w, dist + w});
    }
    
    return -1;
}

signed main()
{
    cin >> n >> m;
    for (int i = 1; i <= m; i ++ )
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a].push_back({b, c});
        rg[b].push_back({a, c});
    }
    cin >> S >> T >> K;
    
    dijkstra();
    if (S == T) K ++ ;
    
    cout << a_star() << '\n';
    
    return 0;
}

8.DFS 之连通性模型、搜索顺序

基础。
搜索顺序这个经常在剪枝中被用到。

9.DFS 之剪枝与优化

一般来说剪枝可以从搜索顺序、当前答案与已有最有答案比较和状态更新时的顺序等方面优化。

数独

/*
太糖了。
不写了,不写了,这dfs剪枝纯唐。
*/
#include <bits/stdc++.h>
#define lowbit(i) (i & -i)

using namespace std;

const int N = 9;

int n = 9, g[N][N];
int r[N], c[N], cel[N][N];
int zero[1 << N];
string str;
vector<int> v[1 << N];
int cn;
unordered_map<int, int> mp;

void draw(int x, int y, int t)
{
    r[x] ^= (1 << t);
    c[y] ^= (1 << t);
    cel[x / 3][y / 3] ^= (1 << t);
}

bool dfs(int cnt)
{
    // cn++;
    // if (cn == 100) return true;
    if (!cnt) return true;
    
    int mn = 10, ki, kj;
    for (int i = 0, k = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ , k ++ )
            if (str[k] == '.')
            {
                int remain = zero[r[i] | c[j] | cel[i / 3][j / 3]];
                if (mn > remain)
                {
                    mn = remain;
                    ki = i, kj = j;
                }
            }
    int state = r[ki] | c[kj] | cel[ki / 3][kj / 3];
    for (int i : v[state])
    {
        auto ch = str[ki * n + kj];
        str[ki * n + kj] = '1' + i;
        draw(ki, kj, i);
        if (dfs(cnt - 1)) return true;
        draw(ki, kj, i);
        str[ki * n + kj] = ch;
    }
    
    return false;
}

signed main()
{
    for (int i = 0; i < (1 << N); i ++ ) 
        for (int j = 0; j < N; j ++ )
            if (!((1 << j) & i)) zero[i] ++ ;
    for (int i = 0; i < n; i ++ ) mp[1 << i] = i;
    for (int i = 0; i < (1 << N); i ++ )
        for (int j = 0; j < 9; j ++ )
            if (!((1 << j) & i)) v[i].push_back(j);
    while (cin >> str, str != "end")
    {
        memset(r, 0, sizeof r);
        memset(c, 0, sizeof c);
        memset(cel, 0, sizeof cel);
        int cnt = 0;
        for (int i = 0, k = 0; i < n; i ++ )
            for (int j = 0; j < n; j ++ , k ++ )
                if (str[k] != '.')
                    draw(i, j, str[k] - '1');
                else cnt ++ ;
        dfs(cnt);
        
        cout << str << '\n';
    }
    
    return 0;
}

10.迭代加深

设置每次 DFS 的层数,使得搜索的节点个数尽量少。
复杂度,感性理解,上一层比下一层的节点一定是少得多的。
于是最终复杂度可以将之前的看做常数,只算最后一层的复杂度。

加成序列

/*
迭代加深。
算是比较重要吧,但是思维难度不高。
*/
#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int n;
int path[N];

bool dfs(int u, int max_dep)
{
    if (u > max_dep) return path[u - 1] == n;
    
    bool st[N] = {0};
    
    for (int i = u - 1; i; i -- )
        for (int j = i; j; j -- )
        {
            int sum = path[i] + path[j];
            if (sum < path[u - 1] || sum > n || st[sum]) continue;
            st[sum] = 1;
            path[u] = sum;
            if (dfs(u + 1, max_dep)) return true;
        }
    return false;
}

signed main()
{
    while (cin >> n, n)
    {
        path[1] = 1;
        int depth = 1;
        while (!dfs(2, depth)) depth ++ ;
        
        for (int i = 1; i <= depth; i ++ ) cout << path[i] << ' ';
        cout << '\n';
    }
    
    return 0;
}

11.双向 DFS

不是很难写,先 DFS 前一半,统计前一半的方案,然后在看后一半,大部分是用到二分的,所以平衡一下会更快。

12.IDA*

在迭代加深的基础上加上 A*,加上估价函数,写法要好写很多。
不过可能爆栈空间,一般和迭代加深一样,处理保证有解问题。
估价函数设计也还行吧。

排书

/*
IDA_Star的难点就在于估价函数。
不过一般都是比较容易就可以设计出来的。
而这题有点困难,要想到用后继关系。
*/
#include <bits/stdc++.h>

using namespace std;

const int N = 20;

int n;
int q[N], tmp[5][N];

int f()
{
    int res = 0;
    for (int i = 1; i < n; i ++ )
        if (q[i + 1] != q[i] + 1)
            res ++ ;
    return (res + 2) / 3;
}

bool check()
{
    for (int i = 1; i <= n; i ++ )
        if (q[i] != i) return false;
        
    return true;
}

bool dfs(int u, int max_dep)
{
    if (u + f() > max_dep) return false;
    if (check()) return true;

    for (int l = 1; l <= n; l ++ )
        for (int r = l; r <= n; r ++ )
            for (int k = r + 1; k <= n; k ++ )
            {
                memcpy(tmp[u], q, sizeof q);
                int x, y;
                for (x = r + 1, y = l; x <= k; x ++, y ++ ) q[y] = tmp[u][x];
                for (x = l; x <= r; x ++, y ++ ) q[y] = tmp[u][x];
                if (dfs(u + 1, max_dep)) return true;
                memcpy(q, tmp[u], sizeof q);
            }

    return false;
}

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

        int depth = 0;
        while (depth < 5 && !dfs(0, depth)) depth ++ ;
        if (depth >= 5) puts("5 or more");
        else cout << depth << '\n';
    }

    return 0;
}
posted @ 2025-04-06 10:20  MafuyuQWQ  阅读(13)  评论(0)    收藏  举报