1 广搜
1)ab路线
https://www.luogu.com.cn/problem/P9425
特殊的是 这里定义了queue<array<int, 3>> q;队列里每个点是一个数组,这样能保存上一个点的状态。
我们不需要判断走的这个是a是b,只需要判断和上一个走的点的关系(上一个走过的点更好取)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, m, k, dx[] = { 0, 1, 0, -1 }, dy[] = { -1, 0, 1, 0 }, ans;
bool st[1001][1001][11];
int main() {
cin >> n >> m >> k;
vector<string> s(n);
for (int i = 0;i < n;i++) cin >> s[i];
queue<array<int, 3>> q;
q.push({ 0, 0, 1 });
st[0][0][1] = true;
while (q.size()) {
int size = q.size();
while (size--) {
auto p = q.front();
q.pop();
int x = p[0], y = p[1];
if (x == n - 1 && y == m - 1) {
cout << ans;
return 0;
}
if (p[2] == k) {
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 && s[a][b] != s[x][y] && !st[a][b][1]) {
st[a][b][1] = true;
q.push({ a, b, 1 });
}
}
}
else {
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 && s[a][b] == s[x][y] && !st[a][b][p[2] + 1]) {
//为什么st三维,因为不同k走到一个点是不一样的,有的步数可能不能到达。
//虽然都是一个点,但是不一样。普通的广搜如果是一个点多次出现,那前面的肯定能到大,所以当时是二维。在这道题目中,每个格子最多可以被访问 k 次。
st[a][b][p[2] + 1] = true;
q.push({ a, b, p[2] + 1 });
}
}
}
}
ans++; 相当于这一层都入队了,都走完了,该走下一层了,所以++
}
cout << -1;
return 0;
}
自己写的ac
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1010;
queue<array<int, 3>> q;
bool st[maxn][maxn][12];
string g[maxn];
int dx[] = { 0,0,1,-1 };
int dy[] = { 1,-1,0,0 };
int n, m, k;
string s[maxn];
int ans;
bool flag;
void bfs(int a,int b)
{
st[a][b][1] = true;
q.push({ a,b,1 });
while (q.size())
{
int size = q.size();
while (size--)
{
auto t = q.front();
q.pop();
int x = t[0];
int y = t[1];
int cnt = t[2];
if (x == n - 1 && y == m - 1) {
cout << ans;
flag = true;
return;
}
if (cnt == k) {
for (int i = 0;i < 4;i++)
{
int x1 = x + dx[i];
int y1 = y + dy[i];
if (st[x1][y1][1])continue;
if (x1 < 0 || y1 < 0 || x1 >= n || y1 >= m)continue;
//注意要进行越界检查
if (s[x][y] == s[x1][y1])continue;
st[x1][y1][1] = true;
q.push({ x1,y1,1 });
}
}
else {
for (int i = 0;i < 4;i++)
{
int x1 = x + dx[i];
int y1 = y + dy[i];
if (st[x1][y1][t[2]+1])continue;
//注意检查条件,注意边界谁是行谁是列
if (x1 < 0 || y1 < 0 || x1 >= n || y1 >= m)continue;
if (s[x][y] != s[x1][y1])continue;
st[x1][y1][t[2]+1] = true;
q.push({ x1,y1,t[2]+1});
}
}
}
ans++;
}
}
signed main()
{
cin >> n >> m >> k;
for (int i = 0;i < n;i++)
cin >> s[i];
bfs(0, 0);
if (!flag)cout << -1 << endl;
}
2)旋转九宫格
https://www.luogu.com.cn/problem/P10578
点击查看代码
#include <bits/stdc++.h>
using namespace std;
string t = "123456789";
//二维变成一维
map<string, int> h;
//用map存的是否访问过
void bfs()
{
queue<string> q;
q.push(t);
//目标状态加入队列,目标状态的步数设为1
h[t] = 1;
while (q.size())
{
string u = q.front();
q.pop();
string v[4] = { u,u,u,u };
//生成 4 个新状态 v[0] 到 v[3],分别对应 33 九宫格中 4 个 22 子矩阵逆时针旋转后的状态
v[0][0] = u[1], v[0][1] = u[4], v[0][3] = u[0], v[0][4] = u[3]; // 左上角
v[1][1] = u[2], v[1][2] = u[5], v[1][4] = u[1], v[1][5] = u[4]; // 右上角
v[2][3] = u[4], v[2][4] = u[7], v[2][6] = u[3], v[2][7] = u[6]; // 左下角
v[3][4] = u[5], v[3][5] = u[8], v[3][7] = u[4], v[3][8] = u[7]; // 右下角
// 注意这是一个逆推的过程,要逆时针转
//对于每个新状态 v[i],若该状态未被访问过(即 h[v[i]] 为 0),则将其步数设为当前状态 u 的步数加 1,然后把该状态加入队列
//相当于把这个矩阵每一次旋转都枚举出来,东西南北加入,然后再遍历(东)东西南北
for (int i = 0; i < 4; i++)
if (!h[v[i]])
{
h[v[i]] = h[u] + 1;
if (v[i] == t) break;
q.push(v[i]);
}
}
}
int main()
{
int _ = 1;
scanf("%d", &_);
bfs(); // 从终点开始预找出所有可能结果的步数
while (_--)
{
string s;
for (int i = 0; i < 9; i++)
{
char c;
scanf(" %c", &c);
s += c;
}
printf("%d\n", h[s] - 1);
}
return 0;
}
3)全球变暖
不懂
https://www.luogu.com.cn/problem/P8662
4)岛屿个数
https://www.lanqiao.cn/problems/3513/learning/
不懂:为啥海水要搜索八个方向,不然遍历不到所有外海。
多个测试样例之间忘记重置,还有多个广搜不能用一个队列
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int maxn = 55;
int m, n,ans;
char g[maxn][maxn];
int st[maxn][maxn];
int dx[] = { 0,0,1,-1,1,1,-1,-1 };
int dy[] = { 1,-1,0,0,-1,1,1,-1 };
void dfs2(int a, int b)
{
queue<pii> q;
st[a][b] = 1;
q.push({ a,b });
while (q.size())
{
auto t = q.front();
int x = t.first;
//不用加括号!!!
int y = t.second;
q.pop();
for (int i = 0;i < 4;i++)
{
int x1 = dx[i] + x;
int y1 = dy[i] + y;
if (st[x1][y1] == 1)continue;
if (x1<0 || y1<0 || x1>m+1|| y1>n+1)continue;
if (g[x1][y1] == '0')continue;
q.push({ x1,y1 });
st[x1][y1] = 1;
}
}
return;
}
void dfs1(int a, int b)
{
queue<pii> q;
//定义全局的队列是不对的
st[a][b] = 1;
q.push({ a,b });
while (q.size())
{
auto t = q.front();
int x =t.first;
int y =t.second;
q.pop();
for (int i = 0;i < 8;i++)
{
int x1 = dx[i] + x;
int y1 = dy[i] + y;
if (st[x1][y1] == 1)continue;
if (x1<0 || y1<0 || x1>m+1 || y1>n+1 )continue;
//因为两边各添了0,所以应该是>n+1;
if (g[x1][y1] == '1')
{
dfs2(x1, y1);
ans++;
}
else {
q.push({ x1,y1 });
st[x1][y1] = 1;
//不管是海水还是岛屿只要访问过就不用访问了,不需要设置两个st数组
//否则dfs2时还需要判断st2麻烦
}
}
}
cout << ans << endl;
}
void solve()
{
memset(g, '0', sizeof g);
memset(st, 0, sizeof st);
ans = 0;
//既没有重置ans,也没用重置st!!!!
//导致前一次测试的访问状态残留
//忘记在周围添加一圈海水,因为是字符,所以不会默认填0,而是填空
cin >> m>> n;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
{
cin >> g[i][j];
}
//注意这里的输入是连着的
dfs1(0, 0);
}
signed main()
{
int T;
cin >> T;
while (T--)
solve();
return 0;
}
点击查看代码
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N = 55;
typedef pair<int, int> PII;
char g[N][N];
bool st[N][N];
int T, n, m;
int cnt;
// 8 连通方向数组
int dx[8] = { 1, 0, 0, -1, 1, 1, -1, -1 };
int dy[8] = { 0, 1, -1, 0, 1, -1, 1, -1 };
void bbfs(int x, int y) // 搜陆地的板子(4 连通)
{
queue<PII> q;
q.push({ x, y });
st[x][y] = 1;
while (q.size())
{
auto t = q.front();
q.pop();
for (int i = 0; i < 4; i++) // 四个方向
{
int a = t.first + dx[i];
int b = t.second + dy[i];
if (a >= 0 && a <= n + 1 && b >= 0 && b <= m + 1 && !st[a][b]) // 在合法区间里且没搜过的
{
if (g[a][b] == '1') // 是陆地就进队标记
{
q.push({ a, b });
st[a][b] = 1;
}
}
}
}
}
void bfs(int x, int y) // 搜海水的板子(8 连通)
{
queue<PII> q;
q.push({ x, y });
st[x][y] = 1;
while (q.size())
{
auto t = q.front();
q.pop();
for (int i = 0; i < 8; i++) // 八个方向
{
int a = t.first + dx[i];
int b = t.second + dy[i];
if (a >= 0 && a <= n + 1 && b >= 0 && b <= m + 1 && !st[a][b])
{
if (g[a][b] == '0') // 是海进队
{
q.push({ a, b });
st[a][b] = 1;
}
else // 不是海就是陆地呗,给陆地染色标记,顺便计数
{
bbfs(a,b);
cnt++;
}
}
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while (T--)
{
memset(st, 0, sizeof st); // 因为是有好几组测试数据,每次都要初始化
memset(g, '0', sizeof g);
cnt = 0;
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
cin >> g[i][j];
}
// 在原地图外阔一圈海水
// 防止角落 岛屿卡住搜索的范围
bfs(0, 0); // 直接从外圈海水搜,把海水都搜到后标记,剩下的就是岛
cout << cnt << endl;
}
return 0;
}
2 并查集
int p[maxn];
int find(int x)
{
if (p[x] == x)return x;
return p[x] = find(p[x]);
//这里用find等于找到了父亲的祖宗,因为找的时候递归了find
}
void merge(int x, int y) {
if (find(x) != find(y))
p[find(x)] = find(y);
}
1)不重复数组
https://www.luogu.com.cn/problem/P8686
因为后面遇到重复的是给ai++,而不能是比他小,所以我们可以逆向思维,每遇到重复的就给祖宗++。祖宗代表后面遇到这个树应该设置为几,在这个过程中有的集合合并了
自己写的代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int maxn = 1e5+10;
int a[maxn],p[maxn];
int n;
int find(int x)
{
if (p[x] == x)return x;
//又把判等写成赋值
else return p[x] = find(p[x]);
}
signed main()
{
cin >> n;
for (int i = 1;i <= n;i++)
{cin >> a[i];
}
for (int i = 1;i <= maxn;i++)
{
p[i] = i;
//不能只赋值数组,要不然数组增加到了4,但是4的父亲是0
//1 3 3 6 7
}
for (int i = 1;i <= n;i++)
{
cout << p[find(a[i])]++ << " ";
}
}
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
const int maxn = 1e5 + 10;
int a[maxn];
int p[maxn];
int find(int x)
{
if (p[x] == x)return x;
return p[x] = find(p[x]);
//这里用find等于找到了父亲的祖宗,因为找的时候递归了find
}
void merge(int x, int y) {
if (find(x) != find(y))
p[find(x)] = find(y);
}
int main()
{
cin >> n;
int x;
for (int i = 1;i <= n;i++)
{
cin >> a[i];
}
for (int i = 1;i <= maxn;i++)
{
p[i] = i;
//不能只赋值数组,要不然数组增加到了5,但是5的父亲是0
}
for (int i = 1;i <= n;i++)
{
a[i]=p[find(a[i])]++;
//相当于在增加时把集合合并了,2233 2=2 2的父亲设为3 2=3 3的父亲设为4 3=4
}
for (int i = 1;i <= n;i++)
{
cout << a[i] << " ";
}
}
2)合并植物
https://www.luogu.com.cn/problem/P8654
最简单的并查集应用
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
int a[maxn];
int p[maxn];
int st[maxn];
int ans;
int n,m;
int k;
int find(int x)
{
if (p[x] == x)return x;
return p[x] = find(p[x]);
//这里用find等于找到了父亲的祖宗,因为找的时候递归了find
}
void merge(int x, int y) {
if (find(x) != find(y))
p[find(x)] = find(y);
}
int main()
{
for (int i = 1;i <= maxn-10;i++)
p[i] = i;
cin >> m >> n;
cin >> k;
int x, y;
while (k--)
{
cin >> x >> y;
merge(x, y);
}
for (int i = 1;i <= m * n;i++)
{
if (st[find(i)] == 0)
{ans++;
st[find(i)] = 1;
}
}
cout << ans << endl;
}
3)兔子找同伴
https://www.luogu.com.cn/problem/P10577
对问题的简化思考,先不管它在哪个位置,最终一定是,要么和左边要么和右边,这个取决于谁近。然后分类,假设左边找右边,要么相互靠近,要么一起向右,相互靠近的就是中点是答案。一起向右的那右边的同伴就是左边的同伴
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
#define p first
#define no second
using namespace std;
const int N = 1e5 + 10;
typedef pair<int, int> PII;
int n;
PII t[N];//每只兔子的位置和编号
int fa[N];//fa[i] 表示第 i 只兔子的父节点
int ans[N];//每只兔子完成集结后的最终位置。
int find(int x) // 并查集+路径压缩
{
if (x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= n; i++)
{
scanf("%d", &t[i].p);
t[i].no = i;
}
sort(t + 1, t + n + 1); // 按位置排序
fa[1] = 2, fa[n] = n - 1; // 特判兔子1与n,1的祖宗肯定是2
for (int i = 2; i < n; i++)
{
if (t[i].p - t[i - 1].p <= t[i + 1].p - t[i].p) fa[i] = i - 1;
else fa[i] = i + 1;
}
for (int i = 1; i <= n; i++)
{
if (fa[i] == i + 1 && fa[i + 1] == i) // i与i+1双向奔赴
{
t[i].p = t[i + 1].p = (t[i].p + t[i + 1].p) / 2;
fa[i] = i, fa[i + 1] = i + 1;//为啥要更新父节点为自身:因为我们要区分双向奔赴的兔子和普通兔子
}
}
// 其余兔子最终到达他祖宗的位置
for (int i = 1; i <= n; i++)
if (fa[i] != i) t[i].p = t[find(i)].p;
for (int i = 1; i <= n; i++)
//因为t数组排序了,不是输入时的顺序,所以我们有no保留了输入顺序
ans[t[i].no] = t[i].p;
for (int i = 1; i <= n; i++)
printf("%d ", ans[i]); // 还原答案
return 0;
}
浙公网安备 33010602011771号