「刷题记录」LOJ/一本通提高篇 广搜的优化技巧
题目比较少,难度还可以,至少都能看懂
「打开灯泡 Switch the Lamp On」
题目传送门:打开灯泡 Switch the Lamp On
对于网格图,一个格的四个角可以看作是四个点,连图,跑最短路,这里朴素 \(\text{SPFA}\) 会被卡,对于堆优化的 \(\text{dijkstra}\),如果像我这样开点的话,\(\text{STL}\) 的优先队列会被卡,要手写堆
堆优化的 $\text{dijkstra}$
/*
date: 2022.8.30
worked by yi_fan0305
*/
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 6e5 + 5;
const int inf = ~(1 << 31);
int n, m, cnt, cot;
int h[N], vis[N], dis[N];
struct edge {
int u, v, w, nxt;
} e[N << 2];
struct Heap {
int i, v;
bool operator < (const Heap &the)const {
return v < the.v;
}
} heap[260000 << 2], z;
void inheap(Heap p) {
heap[++cot] = p;
int x = cot;
while (heap[x] < heap[x >> 1]) {
swap(heap[x], heap[x >> 1]);
x >>= 1;
}
return ;
}
Heap outheap() {
int x = 1;
Heap ans = heap[1];
heap[1] = heap[cot--];
while (1) {
x <<= 1;
if (x > cot)
break;
if (x + 1 <= cot && heap[x | 1] < heap[x])
x |= 1;
if (heap[x] < heap[x >> 1])
swap(heap[x], heap[x >> 1]);
else
break;
}
return ans;
}
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
void add(int u, int v, int w) {
e[++cnt].u = u;
e[cnt].v = v;
e[cnt].w = w;
e[cnt].nxt = h[u];
h[u] = cnt;
}
void dij(int st) {
for (int i = 1; i <= (n + 1) * (m + 1); ++i) {
dis[i] = inf;
}
dis[st] = 0;
z.i = st, z.v = 0;
inheap(z);
while (cot) {
Heap x = outheap();
int u = x.i;
if (vis[u])
continue;
vis[u] = 1;
for (int i = h[u]; i; i = e[i].nxt) {
int v = e[i].v, w = e[i].w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
z.i = v, z.v = dis[v];
inheap(z);
}
}
}
}
int main() {
n = read();
m = read();
if ((n + m) & 1) {
printf("NO SOLUTION\n");
return 0;
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
int zs = (i - 1) * (m + 1) + j;
int ys = (i - 1) * (m + 1) + j + 1;
int zx = i * (m + 1) + j;
int yx = i * (m + 1) + j + 1;
char ch;
cin >> ch;
if (ch == '/') {
add(ys, zx, 0);
add(zx, ys, 0);
add(zs, yx, 1);
add(yx, zs, 1);
} else {
add(zs, yx, 0);
add(yx, zs, 0);
add(ys, zx, 1);
add(zx, ys, 1);
}
}
}
dij(1);
printf("%d\n", dis[(n + 1) * (m + 1)]);
return 0;
}
标程是双端队列的bfs
一样是连接,有线路直接相连的边边权为 \(0\),没有直接相连的边权为 \(1\)
优化:搜到边权为 \(0\) 的从前端进入,搜到为 \(1\) 的从后端进入(类似于 \(\text{dijkstra}\) 的贪心)
BFS
/*
date: 2022.8.30
worked by yi_fan0305
*/
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
typedef long long ll;
const int dx[4] = {1, -1, -1, 1};
const int dy[4] = {1, 1, -1, -1};
const int ix[4] = {0, -1, -1, 0};
const int iy[4] = {0, 0, -1, -1};
const char a[5] = "\\/\\/";
deque<int> x, y;
int n, m;
int dis[510][510];
char c[510][510];
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
void bfs() {
memset(dis, 127, sizeof dis);
x.push_back(0);
y.push_back(0);
dis[0][0] = 0;
while (!x.empty()) {
int xx = x.front();
int yy = y.front();
x.pop_front();
y.pop_front();
for (int i = 0; i <= 3; ++i) {
int dnx = xx + dx[i];
int dny = yy + dy[i];
int inx = xx + ix[i];
int iny = yy + iy[i];
if (dnx >= 0 && dnx <= n && dny >= 0 && dny <= m) {
if (a[i] != c[inx][iny]) {
int step = dis[xx][yy] + 1;
if (step < dis[dnx][dny]) {
x.push_back(dnx);
y.push_back(dny);
dis[dnx][dny] = step;
}
} else {
int step = dis[xx][yy];
if (step < dis[dnx][dny]) {
x.push_front(dnx);
y.push_front(dny);
dis[dnx][dny] = step;
}
}
}
}
}
printf("%d\n", dis[n][m]);
}
int main() {
n = read();
m = read();
if ((n + m) & 1) {
printf("NO SOLUTION\n");
return 0;
}
for (int i = 0; i < n; ++i) {
scanf("%s", c[i]);
// cin >> c[i];
}
bfs();
return 0;
}
「魔板」
题目传送门:魔板
思路:感觉像字符串操作,用 string
,每搜到一种情况就入队
点击查看代码
/*
date: 2022.8.30
worked by yi_fan0305
*/
#include <iostream>
#include <cstdio>
#include <queue>
#include <string>
#include <map>
using namespace std;
typedef long long ll;
map<string, string> mp;
string a;
queue<string> q;
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
void A(string str) {
string s = str;
for (int i = 0; i <= 3; ++i) {
swap(str[i], str[7 - i]);
}
if(mp.count(str) == 0) {
q.push(str);
mp[str] = mp[s] + 'A';
}
return ;
}
void B(string str) {
string s = str;
str[0] = s[3], str[1] = s[0], str[2] = s[1], str[3] = s[2];
str[7] = s[4], str[6] = s[7], str[5] = s[6], str[4] = s[5];
if(!mp.count(str)) {
q.push(str);
mp[str] = mp[s] + 'B';
}
return ;
}
void C(string str) {
string s = str;
str[1] = s[6];
str[2] = s[1];
str[5] = s[2];
str[6] = s[5];
if(!mp.count(str)) {
q.push(str);
mp[str] = mp[s] + 'C';
}
return ;
}
void bfs() {
q.push("12345678");
mp["12345678"] = "";
while (!q.empty()) {
A(q.front());
B(q.front());
C(q.front());
if(mp.count(a) != 0) {
int siz = mp[a].size();
printf("%d\n", siz);
cout << mp[a];
return ;
}
q.pop();
}
return ;
}
int main() {
for (int i = 0; i < 8; ++i) {
char ai;
cin >> ai;
a += ai;
}
bfs();
return 0;
}
「Knight Moves」
题目传送门:Knight Moves
思路:和魔板那道题差不多,有 \(8\) 个跳的方向,每一次跳判断是否出界,不出界就入队
点击查看代码
/*
date: 2022.8.30
worked by yi_fan0305
*/
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
typedef long long ll;
const int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
const int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};
int n, l, xq, xz, yq, yz;
int vis[310][310];
struct node {
int x, y, step;
} z;
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
void bfs() {
memset(vis, 0, sizeof vis);
queue<node> q;
q.push(z);
while (!q.empty()) {
int x = q.front().x, y = q.front().y;
int w = q.front().step;
for (int i = 0; i <= 7; ++i) {
int xx = x + dx[i];
int yy = y + dy[i];
if (xx == xz && yy == yz) {
printf("%d\n", w + 1);
return ;
}
if (xx < 0 || xx >= l || yy < 0 || yy >= l || vis[xx][yy])
continue;
vis[xx][yy] = 1;
q.push(node{xx, yy, w + 1});
}
q.pop();
}
}
int main() {
n = read();
for (int i = 1; i <= n; ++i) {
l = read();
z.x = read();
z.y = read();
xz = read();
yz = read();
if(z.x == xz && z.y == yz) printf("0\n");
else bfs();
}
return 0;
}
「棋盘游戏」
题目传送门:棋盘游戏
思路 \(1\) :由于只有 \(0\) 和 \(1\) 两种数字,所以可以转化成一个 \(16\) 位的二进制数,一个棋子有 \(4\) 个交换的方向,判断一下是否合法,合法就再判断产生的新二进制数是否出现过,没出现过就入队
做法一
/*
date: 2022.8.30
worked by yi_fan0305
*/
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 7e5;
const int dx[4] = {0, 1, 0, -1};
const int dy[4] = {1, 0, -1, 0};
int sorceq, sorcez;
int dis[N], vis[N];
struct mat {
int a[5][5];
} s, f;
int _2jz(mat d) {
int x = 0;
for (int i = 1; i <= 4; ++i) {
for (int j = 1; j <= 4; ++j) {
x <<= 1;
x += d.a[i][j];
}
}
return x;
}
void bfs() {
memset(dis, 127, sizeof dis);
dis[sorceq] = 0;
vis[sorceq] = 1;
queue<mat> q;
q.push(s);
while (!q.empty()) {
mat u = q.front();
q.pop();
int sorce = _2jz(u);
if (sorce == sorcez) {
printf("%d\n", dis[sorce]);
return ;
}
for (int i = 1; i <= 4; ++i) {
for (int j = 1; j <= 4; ++j) {
for (int k = 0; k <= 3; ++k) {
if (i + dx[k] < 1 || i + dx[k] > 4)
continue;
if (j + dy[k] < 1 || j + dy[k] > 4)
continue;
swap(u.a[i][j], u.a[i + dx[k]][j + dy[k]]);
int tmp = _2jz(u);
if (vis[tmp]) {
swap(u.a[i][j], u.a[i + dx[k]][j + dy[k]]);
continue;
}
dis[tmp] = dis[sorce] + 1;
vis[tmp] = 1;
q.push(u);
swap(u.a[i][j], u.a[i + dx[k]][j + dy[k]]);
}
}
}
}
}
int main() {
for (int i = 1; i <= 4; ++i) {
for(int j = 1; j <= 4; ++j) {
scanf("%1d", &s.a[i][j]);
}
}
for (int i = 1; i <= 4; ++i) {
for (int j = 1; j <= 4; ++j) {
scanf("%1d", &f.a[i][j]);
}
}
sorceq = _2jz(s);
sorcez = _2jz(f);
bfs();
return 0;
}
思路2:我同学想出来的,可以把这道题转化成模板那道题,对字符串进行操作,具体看代码吧
做法二
/*
date: 2022.8.31
worked by yi_fan0305
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <map>
using namespace std;
typedef long long ll;
string str, en;
map<string, int> mp;
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
void bfs() {
queue<string> q;
q.push(str);
mp[str] = 0;
while (!q.empty()) {
if(mp.count(en) != 0) {
printf("%d\n", mp[en]);
return ;
}
string tmp = q.front();
q.pop();
for (int i = 0; i < 16; ++i) {
string s = tmp;
if (i + 4 < 16 && s[i] != s[i + 4]) {
swap(s[i], s[i + 4]);
if (mp.count(s) == 0) {
mp[s] = mp[tmp] + 1;
q.push(s);
}
if (s == en) {
printf("%d\n", mp[en]);
return ;
}
}
s = tmp;
if (i - 4 >= 0 && s[i] != s[i - 4]) {
swap(s[i], s[i - 4]);
if (!mp.count(s)) {
mp[s] = mp[tmp] + 1;
q.push(s);
}
if (s == en) {
printf("%d\n", mp[en]);
return ;
}
}
s = tmp;
if(i != 3 && i != 7 && i != 11 && i != 15 && s[i] != s[i + 1]) {
swap(s[i], s[i + 1]);
if (!mp.count(s)) {
mp[s] = mp[tmp] + 1;
q.push(s);
}
if (s == en) {
printf("%d\n", mp[en]);
return ;
}
}
s = tmp;
if(i != 0 && i != 4 && i != 8 && i != 12 && s[i] != s[i - 1]) {
swap(s[i], s[i - 1]);
if (!mp.count(s)) {
mp[s] = mp[tmp] + 1;
q.push(s);
}
if (s == en) {
printf("%d\n", mp[en]);
return ;
}
}
}
}
}
int main() {
char ch;
for (int i = 1; i <= 4; ++i) {
for (int j = 1; j <= 4; ++j) {
cin >> ch;
str += ch;
}
}
for (int i = 1; i <= 4; ++i) {
for (int j = 1; j <= 4; ++j) {
cin >> ch;
en += ch;
}
}
bfs();
return 0;
}
「Keyboarding」
题目传送门:Keyboarding
思路:预处理一下,由于题目中说是跳到下一个不相同的键上去,所以预处理一下每个键朝每个方向跳一步会跳到哪个格中
我们设 \(vis\) 数组,表示一个位置 \((x,y)\) 处理哪个信息
依旧是 \(4\) 个方向,枚举 \(4\) 个方向,先判断是否出界,再判断这个键选还是不选,最后都要入队
点击查看代码
/*
date: 2022.8.31
worked by yi_fan0305
*/
#include <iostream>
#include <cstdio>
#include <map>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
const int dx[4] = {0, 1, 0, -1};
const int dy[4] = {1, 0, -1, 0};
int n, m, len;
int vis[100][100];// 现在要处理的信息
char g[100][100], las[10010];
map<string, int> mp;
struct node {
int x, y, step, dis;
} f[5][100][100];
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
void get() {
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
for (int k = 0; k <= 3; ++k) {
int x = i, y = j;
while (g[x][y] == g[x + dx[k]][y + dy[k]]) {// 如果相同,跳过
x += dx[k];
y += dy[k];
}
f[k][i][j] = node{x, y, 0, 0};// 朝k方向跳,下一步跳到哪
}
}
}
}
void bfs() {
memset(vis, 0, sizeof vis);
queue<node> q;
int ans = 0, k = 1;
while (g[1][1] == las[k] && k <= len)// 先判断最左上角的选不选
++k;
q.push(node{1, 1, k, k - 1});
vis[1][1] = k;
while (!q.empty()) {
node u = q.front();
q.pop();
int x = u.x, y = u.y, nxt = u.step;
if(g[x][y] == las[nxt]) {// 如果能匹配上
if (nxt == len) {
ans = u.dis + 1;
printf("%d\n", ans);
return ;
}
vis[x][y] = nxt + 1;
q.push(node{x, y, nxt + 1, u.dis + 1});
}
for (int i = 0; i <= 3; ++i) {
node z = f[i][x][y];
z.x += dx[i], z.y += dy[i];
if (z.x < 1 || z.x > n || z.y < 1 || z.y > m)// 不能越界
continue;
if (vis[z.x][z.y] >= nxt)// 如果该格处理的信息比当前信息更靠后,不更改
continue;
vis[z.x][z.y] = nxt;
q.push(node{z.x, z.y, nxt, u.dis + 1});
}
}
}
int main() {
n = read();
m = read();
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> g[i][j];
}
}
scanf("%s", las + 1);
len = strlen(las + 1);
++len;
las[len] = '*';
get();
bfs();
return 0;
}
「移动玩具」
题目传送门:移动玩具
思路:要什么思路,和棋盘游戏几乎一模一样,直接上代码
点击查看代码
/*
date: 2022.8.31
worked by yi_fan0305
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <map>
using namespace std;
typedef long long ll;
string str, en;
map<string, int> mp;
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
void bfs() {
queue<string> q;
q.push(str);
mp[str] = 0;
while (!q.empty()) {
if(mp.count(en) != 0) {
printf("%d\n", mp[en]);
return ;
}
string tmp = q.front();
q.pop();
for (int i = 0; i < 16; ++i) {
string s = tmp;
if (i + 4 < 16 && s[i] == '1' && s[i + 4] == '0') {
swap(s[i], s[i + 4]);
if (mp.count(s) == 0) {
mp[s] = mp[tmp] + 1;
q.push(s);
}
if (s == en) {
printf("%d\n", mp[en]);
return ;
}
}
s = tmp;
if (i - 4 >= 0 && s[i] == '1' && s[i - 4] == '0') {
swap(s[i], s[i - 4]);
if (!mp.count(s)) {
mp[s] = mp[tmp] + 1;
q.push(s);
}
if (s == en) {
printf("%d\n", mp[en]);
return ;
}
}
s = tmp;
if(i != 3 && i != 7 && i != 11 && i != 15 && s[i] == '1' && s[i + 1] == '0') {
swap(s[i], s[i + 1]);
if (!mp.count(s)) {
mp[s] = mp[tmp] + 1;
q.push(s);
}
if (s == en) {
printf("%d\n", mp[en]);
return ;
}
}
s = tmp;
if(i != 0 && i != 4 && i != 8 && i != 12 && s[i] == '1' && s[i - 1] == '0') {
swap(s[i], s[i - 1]);
if (!mp.count(s)) {
mp[s] = mp[tmp] + 1;
q.push(s);
}
if (s == en) {
printf("%d\n", mp[en]);
return ;
}
}
}
}
}
int main() {
char ch;
for (int i = 1; i <= 4; ++i) {
for (int j = 1; j <= 4; ++j) {
cin >> ch;
str += ch;
}
}
for (int i = 1; i <= 4; ++i) {
for (int j = 1; j <= 4; ++j) {
cin >> ch;
en += ch;
}
}
bfs();
return 0;
}
「山峰和山谷 Ridges and Valleys」
题目传送门:山峰和山谷 Ridges and Valleys
思路:其实就是搜一下周围 \(8\) 个格,判断都比它们高还是比它们低,同时找连通块,具体看代码吧
点击查看代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 1010;
const int dx[8] = {0, 1, 0, -1, 1, 1, -1, -1};
const int dy[8] = {1, 0, -1, 0, 1, -1, -1, 1};
int n, cnt, tot1, tot2;
int a[N][N], id[N][N], vis[N][N];
struct node {
int x, y;
};
queue<node> q;
inline ll read() {
ll x = 0;
int fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
int main() {
n = read();
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
a[i][j] = read();
}
}
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
if (vis[i][j] == 0) {
vis[i][j] = 1;
int fg1 =0, fg2 = 0;
cnt = 1;
q.push({i, j});
while (!q.empty()) {
node t = q.front();
q.pop();
int x = t.x, y = t.y;
for (int i = 0; i < 8; ++ i) {
int xx = x + dx[i], yy = y + dy[i];
if (xx < 1 || yy < 1 || xx > n || yy > n || (vis[xx][yy] && a[x][y] == a[xx][yy])) {
continue;
} else if (a[x][y] == a[xx][yy]) {
q.push({xx, yy});
vis[xx][yy] = true;
cnt ++;
} else {
if (a[xx][yy] > a[x][y]) {
fg1 = true;
} else {
fg2 = true;
}
}
}
}
if (fg1 && !fg2) {
tot1 ++;
}
if (!fg1 && fg2) {
tot2 ++;
}
if (cnt == n * n) {
printf("1 1");
return 0;
}
}
}
}
printf("%d %d\n", tot2, tot1);
return 0;
}
小结
算法部分除了三分外基本完成了,接下来要专练 DP 了