8.14搜索进阶

写在前面

我也不知道为什么写这个但是开这篇博客的时候顺手写了而且我的好多博客几乎都有一个写在前面于是就写了。

然后就是由于 Rain 是长难句大王所以这个神秘博客仅供参考!

蒟蒻缺乏经验可能有地方会对着代码讲………

由于听说大家都过了一遍知识点,于是不再赘述直接讲题!


A*:

image

[Tyvj Feb11] GF打dota

这题让我们求一个次短路。

我们可以用“当前点已经走的距离加最短路”来当估价函数(最短路肯定小于等于实际花费)。

我们先做一遍平凡的 dijkstra 求出每个点到 1 的最短路,再反着做 A*,每次更新到 1 的时候就判断此时的路径长度是否大于 1 到 n 的最短路,然后直接输出第一次达到的结果即可。

形式化地:

1.求出所有的 dis (一遍SPFA/dijkstra).
2.把终点入队.
3.扩展 dis + t 最小的点.
4.如果它是起点并且比最短路长,输出答案.
5.重复 3 直至求出解.

Code!
#include <bits/stdc++.h>
using namespace std;
const int _ = 50010;
int n, m, x, y, z, dis[_], to[_ << 1], nxt[_ << 1], h[_], tot, len[_ << 1];
bool v[_];
inline void add(int x, int y, int z){
	to[++ tot] = y;
	nxt[tot] = h[x];
	h[x] = tot;
	len[tot] = z;
	return ;
}
struct hhh{
	int num, len;
	inline bool operator < (const hhh & x)const{
		return len > x. len;
	}
};
struct rain{
	int num, len;
	inline bool operator < (const rain & x)const{
		return len + dis[num] > x. len + dis[x. num];
	}
};
inline void dijkstra(){
	std::priority_queue<hhh> q;
	for(int i = 1; i <= n; i ++){
		dis[i] = 999999999;
	}
	dis[1] = 0;
	q. push({1, 0});
	while(q. size()){
		hhh x = q. top();
		q. pop();
		if(v[x. num]){
			continue;
		}
		v[x. num] = 1;
		for(int i = h[x. num]; i; i = nxt[i]){
			y = to[i];
			if(dis[y] > dis[x. num] + len[i]){
				dis[y] = dis[x. num] + len[i];
				if(! v[y]){
					q. push({y, dis[y]});
				}
			}
		}
	}
	return ;
}
inline void Astar(){
	std::priority_queue<rain> q;
	q. push({n, 0});
	while(q. size()){
		rain x = q. top();
		q. pop();
		if(x. num == 1){
			if(x. len > dis[n]){
				printf("%d", x. len);
				return ;
			}
		}
		for(int i = h[x. num]; i; i = nxt[i]){
			y = to[i];
			q. push({y, x. len + len[i]});
		}
	}
	return ;
}
int main(){
	scanf("%d%d", & n, & m);
	for(int i = 1; i <= m; i ++){
		scanf("%d%d%d", & x, & y, & z);
		add(x, y, z);
		add(y, x, z);
	}
	scanf("%d", & m);
	dijkstra();
	if(! m){
		printf("%d", dis[n]);
		return 0;
	}
	Astar();
	return 0;
}

牛跑步

这题让我们求 k 短路,还要分别输出长度们。

只能走下坡路说明它是有向图。

于是我们先建一个反图预处理出最短路,估价函数同上题,第 i 到达终点的答案就是第 i 短路。

Code!
#include <bits/stdc++.h>
using namespace std;
const int _ = 1e6 + 10;
int k, z, dis[_], n, m, x, y, to[_], nxt[_], h[_], ot[_], txn[_], u[_], tot, oto, len[_], nel[_];
bool v[_];
inline void dda(int x, int y, int z){
	ot[++ oto] = y;
	txn[oto] = u[x];
	u[x] = oto;
	nel[oto] = z;
	return ;
}
inline void add(int x, int y, int z){
	to[++ tot] = y;
	nxt[tot] = h[x];
	h[x] = tot;
	len[tot] = z;
	return ;
}
struct hhh{
	int num, len;
	bool operator < (const hhh & x)const{
		return len > x. len;
	}
};
struct rain{
	int num, len;
	bool operator < (const rain & x)const{
		return len + dis[num] > x. len + dis[x. num];
	}
};
inline void dijkstra(){
	std::priority_queue<hhh> q;
	for(int i = 1; i <= n; i ++){
		dis[i] = 999999999;
	}
	dis[1] = 0;
	q. push({1, 0});
	while(q. size()){
		hhh x = q. top();
		q. pop();
		if(v[x. num]){
			continue;
		}
		v[x. num] = 1;
		for(int i = u[x. num]; i; i = txn[i]){
			y = ot[i];
			if(dis[y] > dis[x. num] + nel[i]){
				dis[y] = dis[x. num] + nel[i];
				if(! v[y]){
					q. push({y, dis[y]});
				}
			}
		}
	}
	return ;
}
inline void Astar(){
	priority_queue<rain> q;
	q. push({n, 0});
	while(q. size()){
		rain x = q. top();
		q. pop();
		if(x. num == 1){
			printf("%d\n", x. len);
			if(! -- k){
				return ;
			}
		}
		for(int i = h[x. num]; i; i = nxt[i]){
			y = to[i];
			q. push({y, x. len + len[i]});
		}
	}
	return ;
}
int main(){
	scanf("%d%d%d", & n, &m, & k);
	for(int i = 1; i <= m; i ++){
		scanf("%d%d%d", & x, & y, & z);
		add(x, y, z);
		dda(y, x, z);
	}
	dijkstra();
	Astar();
	while(k --){
		printf("-1\n");
	}
	return 0;
}

IDA*:

涂满它(Flood-it)

给定一个已染色的 n * n 矩阵,每次可以将与格子(1,1)四联通的所有格子染成选定颜色,求将矩阵全染成同一种颜色的最少次数。
2<=N<=8,颜色数<=6。

我们发现数据范围很小,可能可以暴搜。

但是我们根本就是不知道可能会操作多少次,于是暴搜直接爆炸。

可以用一个数组 \(v\) 预处理出将左上角颜色改成颜色 \(c\) 后能够影响到的所有格子位置(注意是四连通!!!),估价函数就是统计目前从来没遍历到的格子的颜色种类总数(这样一定小于等于真实值,是个正确的估价函数),然后每次修改都跑一遍统计能影响到的格子位置的函数,由于肝硬化使用了逆天的 \(bool\) 数组,于是特判略多,实现时可以将已经遍历过的位置换一个值,这样能少吃特判的史。

Code!
#include <bits/stdc++.h>
using namespace std;
const int dx[] = {0, 0, 1, - 1}, dy[] = {1, - 1, 0, 0};
int n, t, a[11][11], xx, yy;
bool v[11][11], cun[6];
inline int h(){
	int as = 0;
	cun[0] = cun[1] = cun[2] = cun[3] = cun[4] = cun[5] = 0;
	for(int i = 1; i <= n; i ++){
		for(int j = 1; j <= n; j ++){
			if(! cun[a[i][j]] && ! v[i][j]){
				as ++;
				cun[a[i][j]] = 1;
			}
		}
	}
	return as;
}
inline void yx(int x = 1, int y = 1, int c = a[1][1]){
	v[x][y] = 1;
	for(int i = 0; i < 4; i ++){
		xx = x + dx[i], yy = y + dy[i];
		if(v[xx][yy] == 1 || xx < 1 || xx > n || yy < 1 || yy > n){
			continue;
		}
		if(a[xx][yy] == c){
			yx(xx, yy, c);
		}
	}
	return ;
}
inline int tu(int c){
	int xin = 0;
	for(int i = 1; i <= n; i ++){
		for(int j = 1; j <= n; j ++){
			if(! v[i][j] && a[i][j] == c && (v[i + 1][j] || v[i - 1][j] || v[i][j + 1] || v[i][j - 1])){//就是这个位置的特判其实完全可以避免! 
				xin ++;
				yx(i, j, c);
			}
		}
	}
	return xin;
}
inline bool dfs(int nw = 0){
	if(nw + h() > t){
		return 0;
	}
	if(! h()){
		return 1;
	}
	bool g[11][11];
	for(int i = 0; i <= 5; i ++){
		memcpy(g, v, sizeof(g));
		if(tu(i) && dfs(nw + 1)){
			return 1;
		}
		memcpy(v, g, sizeof(v));//注意回溯!!! 
	}
	return 0;
}
int main(){
while(cin >> n){
	if(! n){
		return 0;
	}
	for(int i = 1; i <= n; i ++){
		for(int j = 1; j <= n; j ++){
			scanf("%d", & a[i][j]);
			v[i][j] = 0;//别忘多测清空!!! 
		}
	}
	yx();
	t = 0;
	while(! dfs()){
		t ++;
	}
	printf("%d\n", t);
}
	return 0;
}

回转游戏(The Rotation Game)

棋盘大小固定,数字种类<=3,保证一定有解。
image
大家真的能把这个神秘题面赤下去吗?

我将询问一下大家意愿,然后决定是否点开它——

这题难点竟然在于预处理qwq………

【说了一些话】

之后我们考虑这个的估价函数怎么搞。【试图调动气氛】

由于估价函数一定是要小于等于真实值的,我们每次操作也至多会让一个不合法的位置变得合法,于是我们将“中间八个格子中不合法的格子数加当前进行了的操作数”作为估价函数。

然后就是板子啦!

Code!
#include <bits/stdc++.h>
using namespace std;
const int cha[] = {7, 8, 9, 12, 13, 16, 17, 18}, ni[] = {5, 4, 7, 6, 1, 0, 3, 2}, movie[8][7] = {
{1, 3, 7, 12, 16, 21, 23},
{2, 4, 9, 13, 18, 22, 24},
{11, 10, 9, 8, 7, 6, 5},
{20, 19, 18, 17, 16, 15, 14},
{24, 22, 18, 13, 9, 4, 2},
{23, 21, 16, 12, 7, 3, 1},
{14, 15, 16, 17, 18, 19, 20},
{5, 6, 7, 8, 9, 10, 11}
};
int cun[5], a[33], t, zou[55];
inline int h(){
	int as = 10086;
	cun[1] = cun[2] = cun[3] = 0;
	for(int i = 0; i < 8; i ++){
		cun[a[cha[i]]] ++;
	}
	for(int i = 1; i <= 3; i ++){
		as = min(as, 8 - cun[i]);
	}
	return as;
}
inline void move(int kind){
	int x = a[movie[kind][0]];
	for(int i = 1; i <= 6; i ++){
		a[movie[kind][i - 1]] = a[movie[kind][i]];
	}
	a[movie[kind][6]] = x;
	return ;
}
inline bool IDstar(int nw = 0, int bfr = 10086){
	if(h() + nw > t){
		return 0;
	}
	if(! h()){
		return 1;
	}
	for(int i = 0; i < 8; i ++){
		if(bfr != ni[i]){//相等了不就白跑两趟了吗。
			move(i);
			zou[nw] = i;//按顺序找的肯定字典序最小。
			if(IDstar(nw + 1, i)){
				return 1;
			}
			move(ni[i]);//跑回去。
		}
	}
	return 0;
}
int main(){
	ios::sync_with_stdio(false), cin. tie(0), cout. tie(0);
while(10086){
	t = 0;
	for(int i = 1; i <= 24; i ++){
		scanf("%d", & a[i]);
		if(! a[i]){
			return 0;
		}
	}
	if(! h()){
		cout << "No moves needed\n" << a[13] << '\n';
		continue;
	}
	while(++ t && ! IDstar()){
	}
	for(int i = 0; i< t; i ++){
		cout << (char)(zou[i] + 'A');
	}
	cout << '\n' << a[13] << '\n';
}
	return 0;
}

排书(Booksort)

比较简单我也不知道讲不讲。

1≤n≤15。
给定一个长度为 n 的排列,每次抽取其中连续的一段,再把这段插入到其他某个位置,通过多次操作使其变为升序,求最小操作数。

如果最少操作次数大于或等于 5 次,则输出 “5 or more”。

发现规定操作次数小于五次,于是直接 IDA*!

我们称 a[i] != a[i - 1] + 1 时,i 这个位置是坏的,显然我们每次进行一个连续段的移动插入操作至多会使三个坏位置变好【绘制了一些图来解释】。

注意操作次数要向上取整呀!

然后一切暴搜就可以了。

Code!
#include <bits/stdc++.h>
using namespace std;
const int _ = 22;
int a[_], cun[8][_], n, t, ed;
inline int h(){
	int as = 0;//有几个位置坏了。
	for(int i = 1; i <= n; i ++){
		if(a[i] != a[i - 1] + 1){
			as ++;
		}
	}
	return (as + 2) / 3;//一次最多修三个坏位置。
}
inline bool dfs(int nw = 0){//墓前改了几次。
	if(nw + h() > ed){
		return 0;
	}
	if(! h()){//好段都是好的。
		return 1;
	}
	for(int kuai = 1; kuai <= n; kuai ++){
		for(int i = 1; i <= n - kuai + 1; i ++){
			int j = i + kuai - 1;
			for(int k = j + 1; k <= n; k ++){
				memcpy(cun[nw], a, sizeof(a));
				int r = i;
				for(int l = j + 1; l <= k; l ++, r ++){
					a[r] = cun[nw][l];
				}
				for(int l = i; l <= j; l ++, r ++){
					a[r] = cun[nw][l];
				}
				if(dfs(nw + 1)){
					return 1;
				}
				memcpy(a, cun[nw], sizeof(a));
			}
		}
	}
	return 0;
}
int main(){
	scanf("%d", & t);
	while(t --){
		scanf("%d", & n);
		ed = - 1;
		for(int i = 1; i <= n; i ++){
			scanf("%d", & a[i]);
		}
		while(++ ed < 5 && ! dfs()){
		}
		if(ed < 5){
			printf("%d\n", ed);
			continue;
		}
		printf("5 or more\n");
	}
	return 0;
}

破坏正方形(Square Destroyer)

image
image
预处理相信大家有比我更轻松的做法。

考虑估价函数怎么搞。

我们按照正方形的边长从小到大枚举每个位置的正方形,如果它是完整的(该正方形每条边本身存在并且没有删除标记)我们就把它的所有边都打上删除标记,操作数加一,直到没有正方形。

估价函数就是“可能的操作数加已经进行的实际操作数”。

但是正方形很多,我们依旧需要一些剪枝。

当我们正在处理某一完整正方形时,肯定是依次尝试拆掉它的每一条边,然后向下递归。如果我们在回溯到该正方形时发现不管杀掉哪一条边都没有找到解,那么我们不应该在当前深度继续枚举正方形,而应该直接退出。

因为我们是从小到大处理正方形的,我们枚举到的这个正方形肯定是是现存的最小的正方形之一。我们暴力地尝试拆除所有边,都不能在规定的步数内有解,那么这个正方形拆不掉,一定无解。

Code!
#include <bits/stdc++.h>
using namespace std;
const int _ = 66;
int n, cut[_], kkk, t, x, ed, cun[66];
inline int up(int x, int y){
	return (n << 1 | 1) * (x - 1) 	  + y	 ;
}
//,side,
//up side
inline int down(int x, int y){
	return (n << 1 | 1) *  x	      + y	 ;
}
//;upside down side up and down;
inline int left(int x, int y){
	return (n << 1 | 1) * (x - 1) + n + y	 ;
}
//,side,
//left side
inline int right(int x, int y){
	return (n << 1 | 1) * (x - 1) + n + y + 1;
}
//;left right up down
//YOU AND ME!
//ときめく心のモーションが
//あなたに共鸣して止まないの!
//合理とは真逆のプログラム
//知りたい 知りたい
//ねえもっと 付きあって!
//ダンスロボットダンス
//もーいいかい?
//ダンスロボットダンス
//まーだだよ
//ダンスロボットダンス
//もーいいかい?
//ダンスロボットダンス
//もーちょっと!
inline bool zhengfx(int xa, int ya, int xb, int yb){
	for(int i = up(xa, ya); i <= up(xa, yb); i ++){
		if(cut[i]){
			return 0;
		}
	}
	for(int i = down(xb, ya); i <= down(xb, yb); i ++){
		if(cut[i]){
			return 0;
		}
	}
	for(int i = left(xa, ya); i <= left(xb, ya); i += (n << 1 | 1)){
		if(cut[i]){
			return 0;
		}
	}
	for(int i = right(xa, yb); i <= right(xb, yb); i += (n << 1 | 1)){
		if(cut[i]){
			return 0;
		}
	}
	return 1;
}
inline int h(){
	int as = 0;
	memcpy(cun, cut, sizeof(cut));
	for(int kuai = 1; kuai <= n; kuai ++){
		for(int i = 1; i <= n - kuai + 1; i ++){
			for(int j = 1; j <= n - kuai + 1; j ++){
				int xa = i, ya = j, xb = i + kuai - 1, yb = j + kuai - 1;
				if(zhengfx(i, j, i + kuai - 1, j + kuai - 1)){
					as ++;
					for(int i = up(xa, ya); i <= up(xa, yb); i ++){
						cut[i] = 1;
					}
					for(int i = down(xb, ya); i <= down(xb, yb); i ++){
						cut[i] = 1;
					}
					for(int i = left(xa, ya); i <= left(xb, ya); i += (n << 1 | 1)){
						cut[i] = 1;
					}
					for(int i = right(xa, yb); i <= right(xb, yb); i += (n << 1 | 1)){
						cut[i] = 1;
					}
				}
			}
		}
	}
	memcpy(cut, cun, sizeof(cut));
	return as;
}
inline bool IDstar(int nw = 0){
	if(nw + h() > ed){
		return 0;
	}
	if(nw == ed){
		for(int kuai = 1; kuai <= n; kuai ++){
			for(int i = 1; i <= n - kuai + 1; i ++){
				for(int j = 1; j <= n - kuai + 1; j ++){
					if(zhengfx(i, j, i + kuai - 1, j + kuai - 1)){
						return 0;
					}
				}
			}
		}
		return 1;
	}
	nw ++;
	for(int kuai = 1; kuai <= n; kuai ++){
		for(int i = 1; i <= n - kuai + 1; i ++){
			for(int j = 1; j <= n - kuai + 1; j ++){
				int xa = i, xb = i + kuai - 1, ya = j, yb = j + kuai - 1;
				if(zhengfx(xa, ya, xb, yb)){
					for(int k = up(xa, ya); k <= up(xa, yb); k ++){
						cut[k] = 1;
						if(IDstar(nw)){
							return 1;
						}
						cut[k] = 0;
					}
					for(int k = down(xb, ya); k <= down(xb, yb); k ++){
						cut[k] = 1;
						if(IDstar(nw)){
							return 1;
						}
						cut[k] = 0;
					}
					for(int k = left(xa, ya); k <= left(xb, ya); k += (n << 1 | 1)){
						cut[k] = 1;
						if(IDstar(nw)){
							return 1;
						}
						cut[k] = 0;
					}
					for(int k = right(xa, yb); k <= right(xb, yb); k += (n << 1 | 1)){
						cut[k] = 1;
						if(IDstar(nw)){
							return 1;
						}
						cut[k] = 0;
					}
					return 0;
				}
			}
		}
	}
	return 0;
}
int main(){
	scanf("%d", & t);
	while(t --){
		ed = 0;
		memset(cut, 0, sizeof(cut));
		scanf("%d%d", & n, & kkk);
		for(int i = 1; i <= kkk; i ++){
			scanf("%d", & x);
			cut[x] = 1;
		}
		while(! IDstar()){
			ed ++;
		}
		printf("%d\n", ed);
	}
	return 0;
}

The End.

posted @ 2026-08-14 07:10  养鸡大户肝硬化  阅读(28)  评论(0)    收藏  举报