【kuangbin】专题一 简单搜索
【kuangbin】专题一 简单搜索
https://www.acwing.com/activity/content/90/
目录:
1. 棋盘问题
普通dfs问题,注意回溯。
dfs问题就是每种方案都try一下,一直莽到头,然后再挨个回头(回溯)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, k;
ll cnt, res;
char a[10][10];
bool vis[10];
void dfs (int x) {
if (res == 0) {cnt ++; return ;}
if (x == n + 1) return ;
for (int i = 1; i <= n; i ++) {
if (vis[i] || a[x][i] == '.') continue;
vis[i] = true;
res --;
dfs (x + 1);
vis[i] = false;
res ++;
}
dfs (x + 1);
}
int main () {
while (cin >> n >> k, (n+1)||(k+1)) {
memset (vis, false, sizeof vis);
cnt = 0, res = k;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
cin >> a[i][j];
dfs (1);
cout << cnt << endl;
}
}
2. 地牢大师
简单bfs,不同的是坐标是3维的,开三维数组即可
#include <bits/stdc++.h>
using namespace std;
const int N = 105;
char a[N][N][N];
int d[N][N][N];
int l, r, c, sx, sy, sz;
int dx[] = {0, 0, 0, 0, -1, 1};
int dy[] = {1, 0, -1, 0, 0, 0};
int dz[] = {0, 1, 0, -1, 0, 0};
struct Node {
int x, y, z;
};
bool Range (int x, int y, int z) {
if (x > l || x <= 0 || y > r || y <= 0 || z > c || z <= 0)
return false;
return true;
}
void bfs () {
memset (d, 0x3f, sizeof d);
queue <Node> q;
q.push ({sx, sy, sz});
d[sx][sy][sz] = 0;
while (!q.empty ()) {
auto t = q.front ();
q.pop();
int x = t.x, y = t.y, z= t.z;
for (int i = 0; i < 6; i ++) {
int xx = x + dx[i], yy = y + dy[i], zz = z + dz[i];
if (!Range (xx, yy, zz) || d[xx][yy][zz] != 0x3f3f3f3f || a[xx][yy][zz] == '#') continue;
d[xx][yy][zz] = d[x][y][z] + 1;
q.push ({xx, yy, zz});
if (a[xx][yy][zz] == 'E') {
cout << "Escaped in " << d[xx][yy][zz] << " minute(s)." << endl;
return ;
}
}
}
cout << "Trapped!" << endl;
}
int main () {
while (cin >> l >> r >> c, l || r || c) {
for (int i = 1; i <= l; i ++)
for (int j = 1; j <= r; j ++)
for (int k = 1; k <= c; k ++) {
cin >> a[i][j][k];
if (a[i][j][k] == 'S') sx = i, sy = j, sz = k;
}
bfs ();
}
}
3. 抓住那头牛
也是套路化的bfs,把三种操作视为状态,存入dx[]即可
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int d[N];
int st, ed;
bool Range (int x) {
if (x < 0 || x > 1e5) return false;
return true;
}
void bfs () {
memset (d, 0x3f, sizeof d);
d[st] = 0;
queue <int> q;
q.push (st);
while (!q.empty ()) {
int x = q.front ();
q.pop();
int dx[] = {-1, 1, x};
for (int i = 0; i < 3; i ++) {
int xx = x + dx[i];
if (!Range (xx) || d[xx] != 0x3f3f3f3f) continue;
d[xx] = d[x] + 1;
q.push (xx);
if (xx == ed) {
cout << d[xx] << endl;
return ;
}
}
}
}
int main () {
cin >> st >> ed;
if (st == ed) {
cout << 0 << endl;
return 0;
}
bfs ();
}
//bfs经典模型
4. 翻转
个人觉得初接触有点难理解
逻辑:枚举每一种方案,第一行只能靠改变自身;然后依次通过后面的来改变前面的,最后check最后一行是否满足条件(满足则记录为一种方案)
。。。感觉还有些抽象,我建议多模拟几遍代码,代入数据在草稿纸上演算。。因为我也看了好多遍的
对了,还涉及一些位运算的知识,记得滚回去复习
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int N = 20;
int n, m;
char a[N][N], b[N][N];//备用图(在这上面改动) 原图(不变的)
int ans[N][N], tmp[N][N]; //最终方案 临时方案
int dx[] = {0, 1, 0, -1, 0}, dy[] = {0, 0, 1, 0, -1};
bool Range (int x, int y) {
if (x < 0 || x >= n || y < 0 || y >= m)
return false;
return true;
}
void change (int x, int y) {
for (int i = 0; i < 5; i ++) {
int xx = x + dx[i], yy = y + dy[i];
if (Range (xx, yy)) a[xx][yy] ^= 1;
}
tmp[x][y] = 1;
}
void dfs () {
int cnt = 0x3f3f3f3f;
for (int _ = 0; _ < 1 << m; _ ++) {//第一行的情况有2^m种
int step = 0;
memset (tmp, 0, sizeof tmp); //方案置0
memcpy (a, b, sizeof b); //backup, a=b
for (int i = 0; i < m; i ++) {
if (_ >> i & 1) { //保证字典序最小
step ++;
change (0, i);
}
}
for (int i = 1; i < n; i ++)
for (int j = 0; j < m; j ++) {
if (a[i-1][j] == '1') { //根据上一行的情况来改变本行
step ++;
change (i, j);
}
}
bool suc = true;
for (int i = 0; i < m; i ++) {
if (a[n-1][i] == '1') {
suc = false;
break;
}
}
if (suc && step < cnt) {
cnt = step;
memcpy (ans, tmp, sizeof tmp);
}
}
if (cnt == 0x3f3f3f3f) cout << "IMPOSSIBLE\n";
else {
for (int i = 0; i < n; i ++) {
for (int j = 0; j < m; j ++)
cout << ans[i][j] << ' ';
cout << endl;
}
}
}
int main () {
cin >> n >> m;
for (int i = 0; i < n; i ++)
for (int j = 0; j < m; j ++)
cin >> b[i][j];
dfs ();
}
//dfs + 记录方案
5. 找倍数
bfs
按照题目要求,把所有可能一股脑填进去即可
#include <bits/stdc++.h>
using namespace std;
int n;
typedef pair<string, int> psi; //m 余数
void bfs () {
queue <psi> q;
q.push ({"1", 1});
while (!q.empty ()) {
auto t = q.front ();
q.pop ();
if (t.second == 0) {
cout << t.first << endl;
return ;
}
q.push ({t.first + "1", (t.second * 10 + 1) % n});
q.push ({t.first + "0", t.second * 10 % n});
}
}
int main () {
while (cin >> n, n) {
bfs ();
}
}
//找到n的非0倍数(只由01组成)
//所有可能一股脑填进去得了
6. 质数路径
有点暴力的思想,二重循环枚举每一种可能性
#include <bits/stdc++.h>
using namespace std;
int a, b;
const int N = 10000;
int d[N];
int dx[] = {1, 10, 100, 1000};
bool is_prime (int x) {
for (int i = 2; i * i <= x; i ++)
if (x % i == 0) {
return false;
}
return true;
}
void bfs () {
queue <int> q;
memset (d, 0x3f, sizeof d);
d[a] = 0;
q.push ({a});
while (!q.empty ()) {
auto t = q.front ();
q.pop();
if (t == b) {
cout << d[b] << endl;
return ;
}
int num[4];
num[0] = t % 10, num[3] = t / 1000;
num[2] = t / 100 % 10, num[1] = t / 10 % 10;
for (int i = 0; i < 4; i ++)
for (int j = 0; j < 10; j ++) {
if (i == 3 && j == 0) continue; //不足四位
if (j == num[i]) continue;
int tmp = t + (j - num[i]) * dx[i];
if (d[tmp] != 0x3f3f3f3f || !is_prime (tmp)) continue;
d[tmp] = d[t] + 1;
q.push (tmp);
}
}
}
void solve () {
cin >> a >> b;
if (a == b) {
cout << "0\n";
return ;
}
bfs ();
}
int main () {
int t;
cin >> t;
while (t --) {
solve ();
}
}
7. 洗牌
模拟题
呜呜呜太难调了,讨厌模拟题
#include <bits/stdc++.h>
using namespace std;
string change (string a, string b) {
string s;
int n = a.size ();
for (int i = 0; i < n; i ++) s += b[i], s += a[i];
return s;
}
void solve () {
int n, cnt = 1;
cin >> n;
string s1, s2, s3;
cin >> s1 >> s2 >> s3;
set <string> mp;
string t = change (s1, s2);
//mp.insert (t);
while (1) {
if (mp.find (t) != mp.end ()) {
cnt = -1;
break;
}
else if (s3 == t) break;
else mp.insert (t);
t = change (t.substr (0, n), t.substr (n, n));
cnt ++;
}
cout << cnt << endl;
}
int main () {
int t;
cin >> t;
for (int i = 1; i <= t; i ++) {
cout << i << ' ';
solve ();
}
}
//这题怕不是模拟吧
//拜托看清楚上下的顺序
8. 罐子
这道题目乍一看有些懵,但其实分析一下就会发现是“抽象路径”的bfs,即把操作当做不同的状态来看,然后正常做bfs即可。
记录路径我觉得比较新奇,就是用字符串来储存历史路径。
具体看代码吧,一看就懂的:
#include <bits/stdc++.h>
using namespace std;
const int N = 105; //最大容量
int A, B, C;
string idx[] = {"FILL(1)","FILL(2)","DROP(1)","DROP(2)","POUR(1,2)","POUR(2,1)"};
int d[N][N];
struct pot {
int x, y;
string op; //记录所有历史操作
};
void bfs () {
memset (d, 0x3f, sizeof d);
queue <pot> q;
q.push({0,0,""});
d[0][0]=0;
while (!q.empty()) {
auto t = q.front();
q.pop();
int a = t.x, b=t.y; //原容积
if (a==C || b == C) {
cout << d[a][b] <<endl;
string ans = t.op;
for (int i = 0; i < ans.size(); i ++) cout <<idx[ans[i]-'0'] << endl;
return ;
}
//现容积
int dx[] = {A, a, 0, a, max(0,a-B+b), min(A,a+b)};
int dy[] = {b, B, b, 0, min(B, a+b), max(0,b-A+a)};
for (int i = 0; i < 6; i ++) {
int xx = dx[i], yy = dy[i];
if (d[xx][yy] != 0x3f3f3f3f) continue; //非最短
d[xx][yy] = d[a][b] + 1;
q.push ({xx, yy, t.op+to_string(i)});
}
}
cout << "impossible";
}
int main () {
cin >> A >> B >> C;
bfs ();
}
//FILL(1): t1=A,t2=b;
//FILL(2): t1=a,t2=B;
//DROP(1): t1=0,t2=b;
//DROP(2): t1=a,t2=0;
//pour(1,2):t1=max(0,a-B+b),t2=min(B,a+b);
//pour(2,1):t1=min(A,a+b),t2=max(0,b-A+a);
//抽象路径
//存六种状态
9. 点火游戏
欲哭无泪。。想假了,想复杂了。。。
直接暴力枚举就好
如果想看我的错误思路的话(肯定没有人),可以划到代码最后面,都写在注释里面了
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int N = 15;
int n, m, tot;
int d[N][N];
char a[N][N];
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
bool Range (int x, int y) {
if (x <= 0 || x > n || y <= 0 || y > m) return false;
return true;
}
int bfs (pii x, pii y) {
memset (d, 0x3f, sizeof d);
int ans = 0, cnt = 1;
d[x.first][x.second] = d[y.first][y.second] = 0;
queue<pii> q;
q.push (x);
if (x != y) q.push (y), cnt ++;
while (!q.empty ()) {
auto t = q.front();
q.pop();
for (int i = 0; i < 4; i ++) {
int xx = t.first + dx[i], yy = t.second + dy[i];
if (!Range (xx, yy) || a[xx][yy] != '#' || d[xx][yy] != 0x3f3f3f3f) continue;
d[xx][yy] = d[t.first][t.second] + 1;
ans = max (ans, d[xx][yy]);
q.push ({xx, yy});
cnt ++;
}
}
if (cnt == tot) return ans;
return 1e9;
}
void solve () {
vector <pii> v;
tot = 0; //草地个数
cin >> n >> m;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++) {
cin >> a[i][j];
if (a[i][j] == '#')
v.push_back ({i, j}), tot ++;
}
int ans = 1e9;
for (auto i : v)
for (auto j : v)
ans = min (bfs (i,j), ans);
if (ans == 1e9) ans = -1;
cout << ans << endl;
}
int main () {
int t;
cin >> t;
for (int i = 0; i <t; i ++) {
cout << "Case " << i+1 << ": ";
solve ();
}
}
//对于每一个连通块,一定是从最中间开始燃烧为最优
//因此,画图发现,数量固定则时间固定
//选择两个#,要多久能遍历所有的#
//.视为障碍物
//先找连通块,统计个数n,同时统计每个的size
//n==0||n>2,则impossible
//n=2,输出二者当中较大的那个size/2 //分成两段所以除2
//n=1,输出size/3
//我知道我的问题在哪里了。。
//当出现:
// #..####
// #.#.###
// ##.#.##
//这种情况的时候,看似是两个连通块,但是不能分开处理。。因为中间那个燃烧之后会连上
//所以。。。直接暴力枚举两个点www
//好吧。。看数据范围就知道要暴力了,n才15
10. 起火迷宫
先预处理出每一个被火种覆盖到所需要的时间
然后正常的做bfs(记得时刻更新状态)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int>pii;
const int N = 1005;
char a[N][N];
int dx[]= {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
int n, m, sx, sy, ans;
queue <pii> fire;
int f[N][N], d[N][N]; //火烧到的时间,人走到的时间
bool Range (int x, int y) {
if (x > 0 && x <= n && y > 0 && y <= m)
return true;
return false;
}
void bfs1 () {
//预处理出每一个被火种覆盖到所需时间
while (!fire.empty ()) {
auto t = fire.front ();
fire.pop();
int x = t.first, y = t.second;
for (int i = 0; i < 4; i ++) {
int xx = x + dx[i], yy = y + dy[i];
if (a[xx][yy] == '#' || !Range (xx, yy) || f[xx][yy] != 0x3f3f3f3f) continue;
f[xx][yy] = f[x][y] + 1;
fire.push ({xx, yy});
}
}
}
int bfs2 () {
if (sx == n || sy == m || sx == 1 || sy == 1) return 1; //最开始就能走到的特判一下
queue <pii> q;
q.push ({sx, sy});
d[sx][sy] = 0;
while (!q.empty ()) {
auto t = q.front ();
q.pop();
for (int i = 0; i < 4; i ++) {
int xx = t.first + dx[i], yy = t.second + dy[i];
if (!Range (xx, yy) || a[xx][yy] == '#') continue; //越界或走不到
if (d[t.first][t.second] + 1 >= f[xx][yy] || d[xx][yy] != 0x3f3f3f3f) continue; //大火已经烧到了或非最短路
d[xx][yy] = d[t.first][t.second] + 1;
if (xx == n || yy == m || xx == 1 || yy == 1) return d[xx][yy] + 1;
q.push ({xx, yy});
}
}
return -1;
}
void solve () {
memset (d, 0x3f, sizeof d);
memset (f, 0x3f, sizeof f);
while (!fire.empty ()) fire.pop();
cin >> n >> m;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++) {
cin >> a[i][j];
if (a[i][j] == 'J')
sx = i, sy = j;
if (a[i][j] == 'F')
fire.push ({i, j}), f[i][j] = 0;
}
bfs1 ();
int ans = bfs2 ();
if (ans == -1) cout << "IMPOSSIBLE\n";
else cout << ans << endl;
}
int main () {
int t;
cin >> t;
while (t --) {
solve ();
}
}
//起火了就填上#
//把F存起来,挨个扩散
//TLE 了。。允悲
//先求出所有格子被火覆盖的最短时间(多源bfs, 有多个火种)
11.迷宫问题
bfs + 路径记录(倒着来)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
int n;
const int N = 1005;
int a[N][N], d[N][N];
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
pii ans[N][N];
bool Range (int x, int y) {
if (x >= 0 && x < n && y >= 0 && y < n) return true;
return false;
}
void bfs () {
queue <pii> q;
q.push ({0, 0});
d[0][0] = 0;
while (!q.empty ()) {
auto t = q.front ();
q.pop();
for (int i = 0; i < 4; i ++) {
int xx = t.first + dx[i], yy = t.second + dy[i];
if (d[xx][yy] != 0x3f3f3f3f || a[xx][yy] == 1 || !Range (xx, yy)) continue;
q.push ({xx, yy});
d[xx][yy] = d[t.first][t.second] + 1;
ans[xx][yy] = {t.first, t.second}; //更新路径
if (xx == n - 1 && yy == n - 1) return ;
}
}
}
int main () {
memset (d, 0x3f, sizeof d);
cin >> n;
for (int i = 0; i < n; i ++)
for (int j = 0; j < n; j ++)
cin >> a[i][j];
bfs ();
//关键:输出路径(易错)
cout << "0 0\n";
pii t = ans[n-1][n-1];
vector <pii> q;
while (t.first != 0 || t.second != 0) { //注意是或
q.push_back (t);
//cout << t.first << ' ' << t.second << endl;
t = ans[t.first][t.second];
}
reverse (q.begin (), q.end ());
for (auto i : q) cout << i.first << ' ' << i.second << endl;
cout << n-1 << ' ' << n-1 << endl;
}
//最短路 + 记录路径
12. 石油储备
八连通,找连通块个数,bfs/dfs都可以
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
int n, m;
const int N = 105;
char a[N][N];
int dx[] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dy[] = {-1, 0, 1, -1, 1, -1, 0, 1};
bool Range (int x, int y) {
if (x >= 0 && x < n && y >= 0 && y < m)
return true;
return false;
}
void bfs (int x, int y) {
queue <pii> q;
q.push ({x, y});
while (!q.empty ()) {
auto t = q.front ();
q.pop();
for (int i = 0; i < 8; i ++) {
int xx = t.first + dx[i], yy = t.second + dy[i];
if (a[xx][yy] == '@' && Range (xx, yy)) {
q.push ({xx, yy});
a[xx][yy] = '$';
}
}
}
}
int main () {
while (cin >> n >> m, n || m) {
int cnt = 0;
for (int i = 0; i < n; i ++)
cin >> a[i];
for (int i = 0; i < n; i ++)
for (int j = 0; j < m; j ++)
if (a[i][j] == '@')
bfs (i, j), cnt ++;
cout << cnt << endl;
}
}
//八连通
13. 非常可乐
这题简直和AcWing 4222. 罐子 一模一样
3种杯子,6种操作:
1->2 1->3 2->3
max(0,a+b-n) max(0,a+c-m) a
min(a+b,n) b max(0,b+c-m)
c min(a+c,m) min(b+c,m)
2->1 3->1 3->2
min(s,a+b) min(s,a+c) a
max(0,a+b-s) b min(n,b+c)
c max(0,a+c-s) max(b+c-n,0)
#include <bits/stdc++.h>
using namespace std;
const int N = 105;
int s, n, m;
int d[N][N][N];
struct cola {
int a, b, c;
};
bool check (int a, int b, int c) {
if (a == s/2 && b == s/2 && c == 0) return true;
if (a == s/2 && c == s/2 && b == 0) return true;
if (b == s/2 && c == s/2 && a == 0) return true;
return false;
}
void bfs () {
memset (d, 0x3f, sizeof d);
queue <cola> q;
q.push ({s, 0, 0});
d[s][0][0] = 0;
while (!q.empty ()) {
auto t = q.front ();
q.pop ();
int a = t.a, b = t.b, c = t.c;
int dx[] = {max(0,a+b-n), max(0,a+c-m), a, min(s,a+b), min(s,a+c), a};
int dy[] = {min(a+b,n), b, max(0,b+c-m), max(0,a+b-s), b, min(n,b+c)};
int dz[] = {c, min(a+c,m), min(b+c,m), c, max(0,a+c-s), max(b+c-n,0)};
for (int i = 0; i < 6; i ++) {
int xx = dx[i], yy = dy[i], zz = dz[i];
if (d[xx][yy][zz] != 0x3f3f3f3f) continue;
d[xx][yy][zz] = d[a][b][c] + 1;
q.push ({xx, yy, zz});
if (check (xx, yy, zz)) {
cout << d[xx][yy][zz] << endl;
return ;
}
}
}
cout << "NO\n";
}
int main () {
while (cin >> s >> n >> m, n || m || s) {
if (s & 1) {
cout << "NO\n";
continue;
}
bfs ();
}
}
//3种杯子,6种操作
//1->2 1->3 2->3
//max(0,a+b-n) max(0,a+c-m) a
//min(a+b,n) b max(0,b+c-m)
//c min(a+c,m) min(b+c,m)
//2->1 3->1 3->2
//min(s,a+b) min(s,a+c) a
//max(0,a+b-s) b min(n,b+c)
//c max(0,a+c-s) max(b+c-n,0)
14. 找路
属于是有些笨了。。
一开始写的是对于每个餐厅做两次bfs....然后TLE了(因为要做很多次)
其实只用做两次,一次Y,一次M,然后预处理两个距离再求和即可
#include <bits/stdc++.h>
using namespace std;
const int N = 205;
typedef pair<int, int>pii;
int n, m, sx, sy, fx, fy;
char a[N][N];
int d1[N][N], d2[N][N];
int dx[] = {0, 1, 0, -1}, dy[] = {1,0, -1, 0};
vector <pii> v; //canteen
bool Range (int x, int y) {
if (x <= 0 || x > n || y <= 0 || y > m) return false;
return true;
}
void bfs1() {
memset (d1, 0x3f, sizeof d1);
queue <pii> q;
q.push ({sx, sy});
d1[sx][sy] = 0;
//Y
while (!q.empty ()) {
auto t = q.front ();
q.pop();
for (int i = 0; i < 4; i ++) {
int xx = t.first + dx[i], yy = t.second + dy[i];
if (!Range (xx, yy) || d1[xx][yy] != 0x3f3f3f3f) continue;
if (a[xx][yy] == '#') continue;
d1[xx][yy] = d1[t.first][t.second] + 1;
q.push ({xx, yy});
}
}
}
void bfs2() {
memset (d2, 0x3f, sizeof d2);
queue <pii> q;
q.push ({fx, fy});
d2[fx][fy] = 0;
//M
while (!q.empty ()) {
auto t = q.front ();
q.pop();
for (int i = 0; i < 4; i ++) {
int xx = t.first + dx[i], yy = t.second + dy[i];
if (!Range (xx, yy) || d2[xx][yy] != 0x3f3f3f3f) continue;
if (a[xx][yy] == '#') continue;
d2[xx][yy] = d2[t.first][t.second] + 1;
q.push ({xx, yy});
}
}
}
int main () {
while (cin >> n >> m) {
v.clear();
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++) {
cin >> a[i][j];
if (a[i][j] == 'Y') sx = i, sy = j;
else if (a[i][j] == 'M') fx = i, fy = j;
else if (a[i][j] == '@') v.push_back ({i, j});
}
bfs1 ();
bfs2 ();
int minn = 1e9;
for (auto i : v) {
int x = i.first, y = i.second;
minn = min (minn, d1[x][y] + d2[x][y]);
}
cout << 11ll * minn << endl;
}
}
//对于每一个餐厅都做两次bfs,求和;记录最小值