Loading

「刷题记录」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 了

posted @ 2022-09-01 07:17  yi_fan0305  阅读(105)  评论(0编辑  收藏  举报