首页 > 其他分享 >搜索进阶

搜索进阶

时间:2023-06-11 10:12:07浏览次数:60  
标签:return 进阶 int rest step 搜索 ans

搜索进阶

前提提要

这一章不会有多余的题解,都是例题,当做知识点来讲

前置知识

众所周知,我们写搜索第一步是设计搜索状态,然后再次基础上优化,让他跑的更快

搜索题不要过多纠结于他的时间复杂度,只有 \(\mathcal{O}\)(能过、不能过)这两种时间复杂度(

\(\mathcal{P}art\) 1. 设计搜索状态

略过,应该都知道吧

\(\mathcal{P}art\) 2. 剪枝

经典例题:小木棍子(小波

乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 50。

现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。

给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。

\(\mathcal{S}olvetion\) 1. 普通搜索

我们设 \(dfs(step,rest,last,ans)\) 代表现在拼到 \(step\) 跟棍子,这根棍子还差 \(rest\),上一个木棍长度为 \(last\),容易写出21pts的代码

bool dfs(int ans, int step, int rest, int last) {
	if (step == n) {
		if (rest != ans) return false;
		return true;	
	}
	bool f = false;
	for (int i = n; i >= 1; i --) {
		if (v[i] || a[i] > rest || a[i] > last) continue;
		v[i] = 1;
		if (a[i] == rest) {
			f |= dfs(ans, step + 1, ans, ans);
			if (f == true) return true;
		}else {
			f |= dfs(ans , step + 1, rest - a[i], a[i]);
			if (f == true) return true;
		}
		v[i] = 0;
	} 
	return false;
}

\(\mathcal{S}olvetion\) 2. 剪枝进行时

  • 容易想到,木棍子的长度肯定是总长度的因子
  • 我们想到,更长的棍子灵活性更差,短的棍子代价更高,所以当我们从后往前枚举时,这个长棍子已经可以满足,那就不用再试更小的棍子了,直接返回
  • 第一个棍子必须要评成功,因为它是最长的,他如果这里拼不了,哪里拼的了?
  • 我们发现,如果有一段是连续的,只需要判断第一个棍子,其余棍子直接跳过
  • 有了上面的思路,我们很容易想到用桶排优化些常数

至此我们就完成了这道题,注意点细节

#include <bits/stdc++.h>

using namespace std;

const int MAXN = 70;

int n, a[MAXN], t[MAXN];

int maxl, suml;

bool dfs(int ans, int step, int rest, int last) {
	if (step == n) {
		if (rest != ans) return false;
		return true;
	}
	bool f = false;
	for (int i = min(last, min(rest, maxl)); i >= 1; i --) { if (t[i]) {//注:要和maxl取min哦
		-- t[i];//i为木棍的长度 
		if (i == rest) {
			f |= dfs(ans, step + 1, ans, ans);
			++ t[i];
			return f;
			//如果一个棍子可以刚好容纳进去,肯定是最优
			//如果不行就真的不行了
			//如果存在更小的几个可以拼成一个棍子
			//那么和吧这个棍子拿出去根其他木棍拼等价
		}else {
			f |= dfs(ans , step + 1, rest - i, i);
			++ t[i];
			if (f == true) return true;
		}
		if (rest == ans) return f;
		//如果新的一根棒子连第一根都拼不下,其他的也不行 
		//如果存在第二根棒子可以,那么第一根棒子就永远荒废掉了 
		} 
	}
	return false;
}

int main () {
	cin >> n;
	for (int i = 1; i <= n; i ++) cin >> a[i], t[a[i]] ++, maxl = max(maxl, a[i]), suml += a[i];
	for (int l = maxl; ; l ++) 
		if (suml % l == 0) 
			if (dfs(l, 0, l, l)) 
				cout << l << "\n", exit(0);	
	return 0; 
} 

\(\mathcal{Part}\) 3.双向搜索

总所周知,我们的 \(bfs\) 是从一头搜到另外一头,这就会导致我们要遍历完整个搜索树

但是我们从答案那端也同时搜,那么这个搜索只需要搜一半不到就行了,大大提高了算法的效率

我们这里用八数码为例题讲解

我们使用双向搜索,把每个八数码变成一个具体的状态,然后我们就可以搜索(这道题不用双向搜索也可以过

#include<bits/stdc++.h>

using namespace std;

struct node{
	int w[4][4];
	int init() {
		int h = 0;
		for (int i = 1; i <= 3; i ++)
			for (int j = 1; j <= 3; j ++)
				h = h * 10 + w[i][j];
		return h;
	}
	node(int x) {
		for (int i = 3; i >= 1; i --) 
			for (int j = 3; j >= 1; j --)
				w[i][j] = x % 10, x /= 10;
	}
};

typedef map<int, int> Map;
typedef queue<node> Que;

const int D[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

void bfs(Que &Q1, Que &Q2, Map &M1, Map &M2) {
	node u = Q1.front(); Q1.pop();
	
	int x, y, h1 = u.init();
	for (int i = 1; i <= 3; i ++)
		for (int j = 1; j <= 3; j++)
			if (u.w[i][j] == 0)
				x = i, y = j;
	 
	for (int d = 0; d < 4; d ++) {
		int tx = x + D[d][0], ty = y + D[d][1];
		if (1 <= tx && tx <= 3 && 1 <= ty && ty <= 3) {
			node v = u;
			swap(v.w[x][y], v.w[tx][ty]); 
			int h2 = v.init();
			if (!M1.count(h2)) {
				Q1.push(v), M1[h2] = M1[h1] + 1;
			}
			if (M2.count(h2)) cout << M1[h1] + 1 + M2[h2], exit(0);
		}
	}
}

int main () {
	int s, e = 123804765;
	cin >> s;
	if (s == e) cout << 0 << endl, exit(0);
	Map M1, M2; Que Q1, Q2;
	Q1.push(node{s}), M1[s] = 0;
	Q2.push(node{e}), M2[e] = 0;
	while (!Q1.empty() && !Q2.empty()) {
		bfs(Q1, Q2, M1, M2);
		bfs(Q2, Q1, M2, M1);
	}
	return 0;
} 

\(\mathcal{Part}\) 4.折半搜索

这种题和状态压缩可以结合在一起,也可以和搜索

在 \(n\) 的规模比较小的时候,我们一般考虑状态压缩和搜索,但正巧 \(n \le 40\) 超过了范围,但是 \(\cfrac{n}{2}\) 没有,我们这时就可以考虑折半搜索

我们这里用 P4799 [CEOI2015 Day2] 世界冰球锦标赛 作为例题讲解

如果考虑用朴素的背包,时间复杂度为 \(\mathcal{O}(nm)\),这里的 \(m\) 特别大,但是 \(n\) 特别小,但是又不能直接使用搜索,考虑折半

我们讲前 \(\lfloor \cfrac{n}{2}\rfloor\) 作为一组,将合为 \(i\) 的状态记下来,剩下的同理

我们考虑怎么合并搜索树

我们任取左边的一个状态,使用二分搜索找到 \(a+b> m\) 的第一个值,他前面的都是可以的

至此完成了题目

#include<bits/stdc++.h>

using namespace std;

const int MAXN = 40 + 7;
const int MAXM = (1 << 20 + 3);

typedef long long ll;

int  n, t;
ll a[MAXN], m, W[MAXM];

int main () {
	cin >> n >> m;
	for (int i = 1; i <= n; i ++) cin >> a[i];
	int p = n / 2, u = 1 << p, v = 1 << n - p;
	for (int i = 0; i < u; i ++) {
		ll w = 0;
		for (int j = 1; j <= p; j ++) 
			if (i & (1 << j - 1)) w += a[j];
		W[++t] = w;
	}
	sort(W + 1, W + 1 + t);
	
	ll ans = 0;
	for (int i = 0; i < v; i ++) {
		ll w = 0;
		for (int j = 1; j <= n - p; j ++) 
			if (i & (1 << j - 1)) w += a[p + j];
		ans += upper_bound(W + 1, W + 1 + t, m - w) - W - 1;
	}
	cout << ans << endl;
	return 0;
} 

\(\mathcal{Part}\) 5.迭代加深搜索

在某些问题我们需要用 \(dfs\) 来解决问题时,发现搜索树无限宽或者无限深

这时,我们可以使用迭代加深搜索来进行剪枝,限制深度,可能会有意想不到的剪枝哦

这里我们用P1763 埃及分数进行讲解

我们设 \(limit\) 为这次搜索的最深深度,因为我们是从大到小进行枚举,设这一层最小分数为 \(\cfrac{1}{x}\)

则其天然满足(设 \(last\) 为上一个选的数,\(rest\) 为剩下的分数),

\[\begin{cases} \cfrac{1}{x} &< \cfrac{1}{last}\\ \cfrac{1}{x} &\le rest\\ \cfrac{1}{x}\times(limit-depth)&\ge rest \end{cases} \]

这下,我们就可以写了

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

struct Frac{
	ll a, b;
	Frac(ll _a = 0, ll _b = 1) : a(_a), b(_b){};
	inline Frac maintain() {
		ll d = __gcd(a, b); a /= d, b /= d;
		return *this;//返回本身 
	}
	inline Frac operator -(Frac x) {
		ll d = __gcd(b, x.b);
		ll ta = x.b / d * a - b / d * x.a;
		ll tb = b / d * x.b;
		//不能先乘后除,不然会炸 
		return Frac(ta, tb).maintain(); 
	}
};

const int MAXN = 1e2 + 7;

int A[MAXN], R[MAXN];

bool dfs(int limit, int depth, Frac rest, int last) {
	if (depth > limit) return false;
	int l = max(1ll * last + 1ll, rest.b / rest.a + !!(rest.b % rest.a));//向上取	整 
	int r = rest.b * (limit - depth) / rest.a;
	bool f = 0;
	for (int i = l; i <= r; i ++) {
		A[depth + 1] = i;
		if (rest.a == 1 && rest.b == i) {
			if (R[limit] == 0 || A[limit] < R[limit]) 
				for (int j = 1; j <= limit; j ++) R[j] = A[j];
			return true;
		}
		f |= dfs(limit, depth + 1, rest - Frac(1, i), i);
	}
	return f;
} 

int main () {
	int a, b;
	cin >> a >> b;
	for (int limit = 1; ; limit ++) {
		if (dfs(limit, 0, Frac(a, b).maintain(), 1)) {
			for (int j = 1; j <= limit; j ++) 
				cout << R[j] << " ";
			return 0;
		}
	}
		
	return 0;
} 

\(\mathcal{P}art\) 6. \(A^*\) 和 \(Ida^*\)

我们在进行搜索的时候,总是盲目的乱搜,找不到一个搜索的方向,这就需要我们引出估价函数了

我们记估价函数 \(g(x)\) 和最优状态 \(h(s)\),估价函数满足 \(g(s)\le h(s)\)

因此,估价函数被称为“最完美函数”(虽然不太可能达到

  • \(A^*\)

就是在 \(bfs\) 基础上加上估价函数,使用优先队列进行排序,决定搜索的顺序

  • \(Ida*\)

就是在迭代搜索的前提下,加上估价函数,当前 \(depth+g(s) > limit\) 就退出

我们使用P2324 [SCOI2005]骑士精神进行讲解

这道题显而易见的估价函数设计为:

我们设一步回到它的位置,即估价函数的值就为多少匹马不在它该在的位置上

因此我们写出 \(A^*\)

#include<bits/stdc++.h>

using namespace std;

const int E[6][6] = {
	{},
	{0, 1, 1, 1, 1, 1},
	{0, 0, 1, 1, 1, 1},
	{0, 0, 0, 2, 1, 1},
	{0, 0, 0, 0, 0, 1},
	{0, 0, 0, 0, 0, 0}
};

const int D[8][2] = {
	{2, 1}, {2, -1}, {-2, 1}, {-2, -1},
	{1, 2}, {1, -2}, {-1, 2}, {-1, -2}
};

struct Node{
	int w[10][10], x, y, step;
	int init() {//类似哈希 
		int h = 0;
		for (int i = 1; i <= 5; i ++)
			for (int j = 1; j <= 5; j ++)
				h = h * 2 + w[i][j];
		h = h * 6 + x, h = h * 6 + y;
		return h;
	}
	int estimate() {//估价函数 
		int h = 0;
		for (int i = 1; i <= 5; i ++) {
			for (int j = 1; j <= 5; j ++) {
				if (!(i == x && j == y)) 
					h += E[i][j] != w[i][j];
			}
		}
		return h; 
	}
	bool operator <(const Node x) const {return false; }
};

typedef pair<int, Node> Pair;

inline char readchar() {char c; while (!isgraph(c = getchar())); return c;} //以防意外 

int main () {
	int T;
	for (cin >> T; T; T --) {
		priority_queue<Pair, vector<Pair>, greater<Pair> > Q;
		map <int, bool> M;
		Node s; s.step = 0;
		for (int i = 1; i <= 5; i ++)
			for (int j = 1; j <= 5; j ++) {
				char c = readchar();
				if (c == '0') s.w[i][j] = 0;
				else if (c == '1') s.w[i][j] = 1;
				else s.w[i][j] = 0, s.x = i, s.y = j;		
			}
		Q.push(make_pair(0 + s.estimate(), s));
		M[s.init()] = true; bool flag = false;
		while (!Q.empty()) {
			Pair p = Q.top(); Node u = p.second; Q.pop();
			if (u.estimate() == 0) {
				cout << u.step << endl;
				flag = true;
				break;
			}
			if (u.step >= 15) continue;
			for (int d = 0; d < 8; d ++) {
				int tx = u.x + D[d][0], ty = u.y + D[d][1];
				if (1 <= tx && tx <= 5 && 1 <= ty && ty <= 5) {
					Node v = u;
					swap(v.w[u.x][u.y], v.w[tx][ty]);
					v.x = tx, v.y = ty;
					v.step = u.step + 1;
					int e = v.step + v.estimate();
					if (e > 15) continue;
					if (!M.count(v.init())) {
						M[v.init()] = true;
						Q.push(make_pair(e, v));
					}
				}
				
			}
		}
		if (!flag) cout << -1 << endl;
	}	
		
	return 0;
} 

我们写出 \(Ida^*\)

#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
const int INF = 2147483647;
const int E[6][6] = {
    {},
    {0, 1, 1, 1, 1, 1},
    {0, 0, 1, 1, 1, 1},
    {0, 0, 0, 2, 1, 1},
    {0, 0, 0, 0, 0, 1},
    {0, 0, 0, 0, 0, 0}
};
const int D[8][2] = {
    {2, 1}, {2, -1}, {-2, 1}, {-2, -1},
    {1, 2}, {1, -2}, {-1, 2}, {-1, -2}
};
struct Node{
    int W[6][6], x, y, step;  // (x, y) 为空格位置
    int to_int(){
        int h = 0;
        for(int i = 1;i <= 5;++ i)
            for(int j = 1;j <= 5;++ j)
                h = h * 2 + W[i][j];
        h = h * 6 + x;
        h = h * 6 + y;
        return h;
    }
    int estimate(){
        int h = 0;
        for(int i = 1;i <= 5;++ i)
        for(int j = 1;j <= 5;++ j)
            if(!(i == x && j == y))
                h += E[i][j] != W[i][j];
        return h;
    }
    bool operator <(const Node x) const { return false; }
};
using Pair = pair<int, Node>;

char readchar(){
    char c; while(!isgraph(c = getchar())); return c;
}

bool ida(int limit, Node s){
    int e = s.estimate();
    if(e == 0){
        printf("%d\n", s.step); return true;
    } else
    if(s.step + e > limit) return false; else {
        int x = s.x;
        int y = s.y;
        for(int d = 0;d < 8;++ d){
            int nx = x + D[d][0];
            int ny = y + D[d][1];
            if(1 <= nx && nx <= 5 && 1 <= ny && ny <= 5){
                Node v = s;
                swap(v.W[x][y], v.W[nx][ny]);
                v.x = nx;
                v.y = ny;
                v.step = s.step + 1;
                if(ida(limit, v)) return true;
            }
        }
    }
    return false;
}
int main(){
    int T; scanf("%d", &T);
    for(int test = 1;test <= T;++ test){
        Node s; s.step = 0;
        for(int i = 1;i <= 5;++ i)
        for(int j = 1;j <= 5;++ j){
            char c = readchar();
            if(c == '0') s.W[i][j] = 0;
            if(c == '1') s.W[i][j] = 1;
            if(c == '*') s.W[i][j] = 0, s.x = i, s.y = j;
        }
        int ans = 0;
        for(;ans <= 15;++ ans){
            if(ida(ans, s)) break;
        }
        if(ans == 16) puts("-1");
    }
    return 0;
}

后记

搜索题要练习,加油吧

标签:return,进阶,int,rest,step,搜索,ans
From: https://www.cnblogs.com/Phrvth/p/17472550.html

相关文章

  • 微信小程序搜索文档时
    搜索文档在微信开放文档能直接搜标签(https://developers.weixin.qq.com/miniprogram/dev/framework/)uni-app能直接搜方法(https://uniapp.dcloud.net.cn/)......
  • 评价手头输入法或搜索类产品
    导语:在数字化时代,输入法和搜索引擎已经成为我们日常生活中必不可少的工具。无论是在移动设备上输入文本,还是在电脑上进行快速搜索,选择一个高效而舒适的输入法或搜索产品对于提升工作效率和用户体验至关重要。本文将探索手头输入法或搜索类产品的优势、功能和使用体验,帮助你在众多......
  • SpringBoot进阶教程(七十六)多维度排序查询
    在项目中经常能遇到,需要对某些数据集合进行多维度排序的需求。对于集合多条件排序解决方案也有很多,今天我们就介绍一种,思路大致是设置一个分值的集合,这个分值是按照需求来设定大小的,再根据分值的大小对集合排序。v需求背景我们来模拟一个需求,现在需要查询一个用户列表,该列表......
  • 使用Python进行任务调度(进阶篇)
    在上一篇文章使用Python完美管理和调度你的多个任务中,介绍了使用Python+schedule管理和调度任务的入门方法,本文继续介绍任务调度进阶篇。问题描述:启动多个任务之后,由于种种原因,可能需要中途取消某个任务。代码截图:运行效果:......
  • Python借助百度搜索引擎爬取Python小屋密切相关文章
    封面图片:《Python程序设计实验指导书》(ISBN:9787302525790),董付国,清华大学出版社=============第一步,查看本机Chrome浏览器版本。第二步,下载正确版本的Chrome浏览器驱动然后放到Python安装目录中,同时确保Python安装目录在系统环境变量Path中,下载地址为http://chromedriver.storage.go......
  • 头部搜索结构(居中方法)
     中间结构设置方法:1.左边淘宝网(用以图替字实现):首先左边这个设置左浮动2.右边二维码  (用以图替字实现):右边这个再设置右浮动3.中间这个设置居中就会自动的顶上去   中间结构 样式:淘宝网:以图换字 二维码 中间样式 自动选中(后期实现需要JavaScri......
  • Java--进阶
    高级文本处理Locale类 //返回Java所支持的全部国家和语言的数组 Locale[]localeList=Locale.getAvailableLocales(); for(Localelocale:localeList) { System.out.println(locale.getLanguage()+"_"+locale.getCountry()); System.out.println(loc......
  • Vue进阶(幺贰零):父组件获取子组件验证结果
    (文章目录)一、前言在开发Vue项目过程中,代码复用之自定义组件是常做事情。当子组件为form表单的时候,父组件需要获取子组件(表单)的验证结果。尽管有prop和事件,但是有时仍然需要在JavaScript中直接访问子组件。为此可以使用ref为子组件指定一个引用ID。ref被用来给元素或子......
  • [Week 20]每日一题(C++,图论,数学,搜索)
    目录T1[Daimayuan]Collision(C++,多源最短路)题目描述输入描述输出描述样例输入1样例输出1样例输入2样例输处2数据范围解题思路T2[Daimayuan]农田划分(C++,数学,BFS)题目描述题目输入题目输出样例输入1样例输出1样例输入2样例输出2数据范围解题思路T3[Daimayuan]三段式(C++,数组前缀......
  • MDT部署Windows系列 (十二): 进阶篇—制作完美的Win10 22H2系统镜像模板之移除Windows
    前言由于工作等原因(借口),距离发布上一篇MDT系列的文章已经过去一年::twemoji:sweat::上一章我记录了基于MDT如何使用一个Task即可实现制作Windows基础wim镜像+DIY基础软件+捕捉镜像。传送门有好多同学一直咨询在制作捕捉镜像的时候遇到的常见的2个问题:如何彻底的移除掉Windows10中......