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;
}
本文来自博客园,作者:爱朝比奈まふゆ的MafuyuQWQ。 转载请注明原文链接:https://www.cnblogs.com/MafuyuQWQ/p/18810907

浙公网安备 33010602011771号