首页 > 其他分享 >高级搜索学习笔记

高级搜索学习笔记

时间:2023-01-19 10:23:14浏览次数:57  
标签:return val int 高级 笔记 tag 搜索 ans dis

一轮集训DAY6&7&8——高级搜索

主要学习高级搜索:
注:限于篇幅,部分代码食用洛谷剪贴板(但这些题的代码推荐先自己实现)。

高级搜索

其主要亮点在于运用不同的搜索策略达到减少时间复杂度的目的。

迭代加深DFS

看见类似于超过x步输出-1,就可以往迭代加深思考。

其主要板子就是:

void dfs(……,int now){
	if(now>dep)return ;
    ……
}
//main中
	while(!ans){
		dep++;dfs(……,0);
    }

最后的答案就是dep。

这种搜索常用的剪枝就是:

  1. 设计期望最优函数,也即A-Star中的估价函数,含义是在最优(理论上的)的情况下,达到最优解的步数。此时若当前步数加上这个估计步数大于了dep,直接return;

  2. 搜到一个答案马上结束。这是因为迭代加深保证了所搜到的第一个答案是最小步数(类似BFS)

  3. 上下界剪枝

  4. 排除冗余

例题分析

导弹防御系统

题目简述:给定一个序列,将其拆分为尽量少的子序列,每一个子序列要么单调不降要么单调不增。

最优化问题,考虑迭代加深
可以这样考虑:设\(up,down\)数组记录每一个上升/下降子序列的末项,枚举每一个数\(a_i\)应该插入哪一个序列(当然找不到的时候也可以新开一个序列)即可。

就这么简单???——是的,就这么简单。

#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n,a[105],up[105],down[105],ans;
bool dfs(int u,int _up,int _down){
    if(_up+_down>ans)return false;
    if(u==n)return true;
    bool flag=false;
    for(int i=1;i<=_up;i++){
        if(up[i]<a[u]){
            int t=up[i];
            up[i]=a[u];
            if(dfs(u+1,_up,_down))return true;
            up[i]=t;
            flag=true;
            break; 
        }
    }
    if(!flag){
        up[_up+1]=a[u];
        if(dfs(u+1,_up+1,_down))return true;
    }
    flag=false;
    for(int i=1;i<=_down;i++){
        if(down[i]>a[u]){
            int t=down[i];
            down[i]=a[u];
            if(dfs(u+1,_up,_down))return true;
            down[i]=t;
            flag=true;
            break; 
        }
    }
    if(!flag){
        down[_down+1]=a[u];
        if (dfs(u+1,_up,_down+1))return true;
	}
    return false;
}

int main(){
	ios::sync_with_stdio(false);
    while(cin>>n&&n){
        for(int i=0;i<n;i++)cin>>a[i];
       	ans=0;
	    while(!dfs(0,0,0))ans++;
    	cout<<ans<<"\n";
	}
    return 0;
}

巴士

可以考虑先预处理出所有可能的巴士线路,代码如下:

#define re register
#define line inline 
struct node{
	int s,d,siz;
	bool operator<(node b){
		return siz>b.siz;
	}
}b[370000];
line bool check(int s,int d){
	for(re int j=s;j<60;j+=d)if(!num[j])return false;
	return true;
}
line void init(){
	read(n);for(re int i=1;i<=n;i++){int x;read(x);num[x]++;}
	for(re int i=0;i<60;i++)
		for(re int j=i+1;j+i<60;j++)
			if(check(i,j))b[++tot]=(node){i,j,(59-i)/j+1};
	sort(b+1,b+tot+1);
}

然后,我们就食用IDDFS,在每一层枚举食用哪一个线路,并打标记。

接着,你交上了这份无比暴力的代码,发现自己仅有可怜的几分。

接着,我们考虑剪枝。

  1. 最优性剪枝——迭代加深在这里
  2. 可行性剪枝:记录下每条线路最多覆盖多少点,然后用\(sum\)统计后面最多能覆盖多少点,不够就不能选
  3. 优化搜索顺序:考虑优先选择覆盖点数多的线路,这样可以使得搜索树的上层分支变小,从而提升效率(有了这个,优化2的\(sum\)其实是可以不用的)。

不过我最后的代码卡卡也只有83分可恶的yzh那群人加的数据(听说全17是高分)

#define re register
#define line inline 
struct node{
	int s,d,siz;
	bool operator<(node b){
		return siz>b.siz;
	}
}b[370000];
int num[100],tot,dep,n;
line bool check(int s,int d){
	for(re int j=s;j<60;j+=d)if(!num[j])return false;
	return true;
}
line void read(int &x){
	x=0;char ch=getchar();
	while(ch>'9'||ch<'0')ch=getchar();
	while(ch>='0'&&ch<='9')
		x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
line void init(){
	read(n);for(re int i=1;i<=n;i++){int x;read(x);num[x]++;}
	for(re int i=0;i<60;i++)
		for(re int j=i+1;j+i<60;j++)
			if(check(i,j))b[++tot]=(node){i,j,(59-i)/j+1};
	sort(b+1,b+tot+1);
}
bool dfs(int now,int cnt,int cur){
	//cout<<now<<" "<<cnt<<" "<<cur<<endl;
	if(now==dep){
		if(cur==0){
			cout<<dep;exit(0);
		}
		return false;
	}
	if(b[cnt].siz*(dep-now)<cur)return false;
	for(re int i=cnt;i<=tot;++i){
		node x=b[i];
		if(check(x.s,x.d)){
			for(re int i=x.s;i<=59;i+=x.d){
				num[i]--;
				cur--;
			}
			if(dfs(now+1,i,cur))return true;
			for(re int i=x.s;i<=59;i+=x.d){
				num[i]++;
				cur++;
			}
		}
	}
	return false;
}
int main(){
	ios::sync_with_stdio(false);init();
	dep=0;
	while(!dfs(0,1,n)){
		++dep;
		if(dep>=17)break;
	}
	cout<<dep<<endl;
}

Power Calculus

转化一下题意,即为:

求出一个序列:\(a_0=1,a_m=n\),对于\(\forall i\in[1,m]\),需要满足至少存在一对\(j,k\),使得\(a_i=a_j\pm a_k(j,k<i)\),最小化\(m\)。

说人话,就是对于一个首项为1末项为\(n\)的序列,要求序列的每一项都必须是前面的序列中某两项(可以重合)之和/差。

最优化的复杂搜索问题一般考虑迭代加深

首先来证明一个性质:最优方案中:\(j,k\)中至少有一个为\(i-1\)。

证明的话,可以理解为我们是不断将某个\(x^a\)与其他数进行操作拼出\(x^n\),其中\(x^a\)始终是操作数(当然操作后就被更新了。),如果有一次没有他参与,那么如果是其余两个是和/差都可以通过同样的两次操作将\(x^a\)改为操作后的结果,将这个思维过程反过来就可以得出这个结论。

然后就是爆搜吗?不不不——可行性剪枝:即使是每次选最大值,也不过是让当前操作数成倍增长而已,如果将剩下的次数全拿来成倍增长还不能到最终结果的话,就肯定剪掉。
完整代码

折半DFS

这种题很少,一般是将DFS需要枚举的数量减半,分批处理,最后合并答案。所以需要两个DFS

例题分析

送礼物

考虑折半,先搜索前一半的数,将其所能组成的所有不超过\(w\)的和全部存入\(num\),然后排序去重,接着再搜索后面的数,对于每一个和upper_bound一下就行。此题卡常,为了优化空间和搜索顺序,推荐先自大到小排序再进行计算。

#define ll long long
int tot;
ll num[50505050],ans;
int st,n,m,a[1050];
void dfs1(int now,ll sum){
	if(now>st){
		num[++tot]=sum;
		return ;
	}
	ll e=sum+a[now];
	if(e<=m)dfs1(now+1,e);
	dfs1(now+1,sum);
}
void dfs2(int now,int sum){
	if(now>n){
		ans=max(ans,sum+num[upper_bound(num+1,num+tot+1,m-sum)-num-1]);
		return ;
	}
	ll e=sum+a[now];
	if(e<=m)dfs2(now+1,e);
	dfs2(now+1,sum);
}
void read(int &x){
	x=0;char ch=getchar();
	while(ch>'9'||ch<'0')ch=getchar();
	while(ch>='0'&&ch<='9')
		x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int main(){
	read(m);read(n);
	for(int i=1;i<=n;i++)read(a[i]);
	sort(a+1,a+n+1);reverse(a+1,a+n+1);
	st=n/2;
	dfs1(1,0);
	sort(num+1,num+tot+1);
	tot=unique(num+1,num+tot+1)-num-1;
	dfs2(st+1,0);
	cout<<ans<<endl;
}

一般的折半搜索的基本思想就是:算前面一半,再算后面一半,然后在合并两半。这样前面两半的复杂度就少了一个根号,合并复杂度一般根据贪心不会太高。

双向BFS

适用于:有明确的起始状态和最终状态,空间允许进行HASH的题目

其板子大概是:


int bfs(){
//默认s为起点,t为终点,q为队列,vis,dis为标记/距离函数
//get(x)为得到这个节点的HASH值,node为类,mk为状态和方向合并的函数,
//.tag为方向,.val为HASH值
    int S=get(s),T=get(t);
    if(S==T)return 0;
    queue<node>q;
    q.push(mk(s,0));q.push(mk(t,1));
    dis[0][S]=dis[1][T]=0;
    vis[0][S]=vis[1][T]=0; 
    while(!q.empty()){
        node u=q.front();q.pop();
        for(/*枚举状态转移,设v为子状态*/){
            node v;v=/*一系列计算*/;v.tag=u.tag;
            if(check(v)){//合法的话
                if(vis[v.tag^1][v.val]){//找到答案
                    return dis[v.tag^1][v.val]+dis[u.tag][u.val]+1;
                }
                if(vis[v.tag][v.val])continue;
                vis[v.tag][v.val]=1;
                int d=0;
                dis[v.tag][v.val]=dis[u.tag][u.val]+1;
                if(d<=step)q.push(v);//适用于限制步数的题
                //这个操作可以使得有解情况下最多搜step*2+1步,最少搜2*step步
            }
        }
    }
    return -1;
}

解释:我们运用一个队列存储,但这个队列具有三段性,也即最前面一段属于tag,步数为s,中间一段属于tag^1,步数为s+1,后面一段属于tag,步数为s+1(tag=0/1分别表示起点终点)。

一些性质

我们设\(u.tag=0\)为从起点出发的状态,\(u.tag=1\)表示从终点出发的状态,\(u.val\)表示HASH值,设\(f\)是估价函数返回值。
将\(s\)先于\(t\)入队。

这个方法基于双向BFS的三段性。根据双向BFS的搜索树,容易得到若两次都搜索到了v.val这个状态,则\(dis[0][v.val]-dis[1][v.val]\le 1\)
进行分类讨论,设\(v.tag\)是最后结束搜索的状态(也即\(v.tag\oplus 1\)之前就算过):

  1. \(v.tag=0\),容易得到\(dis[0][v.val]=dis[1][v.val]+1\)
  2. \(v.tag=1\),容易得到\(dis[0][v.val]=dis[1][v.val]\)

常见HASH技巧

cantor展开

适用于全排列HASH,它可以将\(1\sim n\)的全排列与\(0\sim n!-1\)一一建立映射。

其方法是,设\(a_1\sim a_n\)是一个排列,\(f(a_i)=\sum _{k=i}^n[a_k>a_i]\),则这个排列映射为的编号为:

\[\sum_{i=1}^nf(a_i)(n-i)! \]

这是这个排列在全排中的字典序排名-1

关于证明,可以思考数位DP,\(f(a_i)(n-i)!\)的组合意义就是第\(i\sim n\)位,将比\(a_i\)大的数放在第\(i\)位,后面的全排列,可以确定这些都比这个排列大。

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
int a[17];
int cantor(int a[],int len){
	int ans=0;
	for(int i=1;i<=len;i++){
		int c=1,m=1,cnt=0;
		for(int j=i+1;j<=len;j++){
			if(a[i]>a[j]){
				++cnt;
			}
			m*=c;c++;
		}
		ans+=cnt*m;
	} 
	return ans+1;
}
signed main(){
	int n;cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	cout<<cantor(a,n);
}

逆康托展开

给定一个排名\(k\)和\(n\),求出\(n\)的全排中排名为\(k\)的排列。

其方法是:将\((n-1)!\)除以\(k\),商为该位数在剩下数中的排名,余数继续进行此类操作。

int jc[10]={1,1,2,6,24,120,720,5040,40320,362880};
void  excantor(int x,int n){
	vector<int>a,b;
	for(int i=1;i<=n;i++)a.push_back(i);
	for(int i=n;i;--i){
		int p=x%jc[i-1],q=x/jc[i-1];
		x=p;
		b.push_back(a[q]);
		a.erase(a.begin()+q);
	}
	for(int i=0;i<n;i++)cout<<b[i];
}
int main(){
	int n,m;cin>>n>>m;
	excantor(n-1,m);
}

STL_map

非常NB,空间仍然只是状态数量,但需要\((\log n)\)的额外时间复杂度(其实问题不大)。

具体的,常用于状态表示的数超过了\(1e7\),亦或者使用字符串存储状态,MAP的用法就不多说了。

进制HASH

也很简单,常见的是二进制,但实际上不止:

其实可以这样想,以走上下左右地图问题为例,每一位可以表示上一次进行的操作是第几个,用四进制状压即可。

对于状态压缩的常见操作:

  1. 取出第\(k\)位:先除再膜
  2. 压入第\(k\)位:膜+除+加+除

这个学过字符串HASH的大家应该都会。

例题分析

一般来说,设计双向BFS算法有以下几个步骤:

  1. 发现双向BFS(有明确初始态和终末态)。
  2. 设计HASH。
  3. 设计状态转移(一般是很套路的东西)。
  4. 开始快乐地敲板子然后就发现挂了

其中比较复杂的是2,3,两步,在调试的时候注意检查HASH是否正确。

骑士精神

首先,因为本题有明确的初始态和终末态,可以考虑双向BFS。

按照步骤,我们先来设计状态和HASH

棋盘5X5,每个点有三个状态,可以考虑三进制HASH,\(3^{25}\)大约在\(2^{45}\)次方左右,记得开long long

当然,此题不卡常,可以考虑使用字符串和map进行存储(主要是方便,用三进制也要map)

状态转移就是普通的走地图。剩下就是套板子啦。

#include<bits/stdc++.h>
using namespace std;
int n,m,a[6][6],sx,sy,ty=3,tx=3;
struct node{
	int a[6][6],tag,l,r;
	string val;
};
map<string,int>vis[2],dis[2];
int lst[6][6]={{0,0,0,0,0,0},{0,1,1,1,1,1},{0,2,1,1,1,1},{0,2,2,0,1,1},{0,2,2,2,2,1},{0,2,2,2,2,2},},dx[8]={1,-1,-1,1,2,-2,-2,2},dy[8]={2,-2,2,-2,1,-1,1,-1};
void init(){
	for(int i=1;i<=5;i++){
		for(int j=1;j<=5;j++){
			char x;cin>>x;
			if(x=='*')a[i][j]=0,sx=i,sy=j;
			if(x=='1')a[i][j]=1;
			if(x=='0')a[i][j]=2;
		}
	}
}
string get(int a[6][6]){
	string ans="";
	for(int i=1;i<=5;i++)for(int j=1;j<=5;j++)ans+=a[i][j]+'0';
	return ans;
}
bool check(int sx,int sy){
	if(min(sx,sy)<1||max(sx,sy)>5)return false;
	return true; 
}
int bfs(){
	node s,t;
	memcpy(s.a,a,sizeof a);memcpy(t.a,lst,sizeof lst);
	s.l=sx,s.r=sy,t.l=tx,t.r=ty;
	s.tag=0,t.tag=1,s.val=get(s.a),t.val=get(t.a);
	vis[0][s.val]=1,vis[1][t.val]=1;
	dis[0][s.val]=dis[1][t.val]=0;
	queue<node>q;
	q.push(s),q.push(t);
	while(!q.empty()){
		node u=q.front();q.pop();
		if(dis[u.tag][u.val]>8)return -1;
		for(int i=0;i<8;i++){
			node v;v.l=u.l+dx[i],v.r=u.r+dy[i];
			if(check(v.l,v.r)){
				swap(u.a[u.l][u.r],u.a[v.l][v.r]);
				memcpy(v.a,u.a,sizeof v.a);
				swap(u.a[u.l][u.r],u.a[v.l][v.r]);
				v.val=get(v.a);
				if(vis[u.tag^1][v.val]){
					return dis[u.tag][u.val]+dis[u.tag^1][v.val]+1;		
				}
				else {
					if(vis[u.tag][v.val])continue;
					vis[u.tag][v.val]=1;
					dis[u.tag][v.val]=dis[u.tag][u.val]+1;
					v.tag=u.tag;
					q.push(v);
				}
			}
		}
	} 
	return -1;
}
int main(){
	int n;cin>>n;
	while(n--){
		vis[1].clear();vis[0].clear();
		dis[1].clear();dis[0].clear();
		init();
		int ans=bfs();if(ans>15)ans=-1;
		cout<<ans<<endl;
	}
}

八数码问题

无比经典,不过这就是双向BFS的板题……。

由于是全排列(把空格看作9),用康托展开HASH即可。

一个有趣的结论是奇数码问题可以使用逆序对来判定。

引理:奇数码问题两个状态可达的充要条件是将空格删去,把8个数排成一列后,两个序列的逆序对数的奇偶性相同

证明:
必要性:显然,左右移动空格逆序对不会变化,上下移动只会有偶数次变化,逆序对奇偶性不变。
充分性:比较困难,这里提供一种简易的证明(错了请指正)

首先\(2*2\)的数码问题是每一个状态都可达的,\(3 * 3\)的数码问题通过计算机可以验证奇/偶集内状态互相可达。

那么:对于\(x*x(x=2k+1)\)的数码问题,可以通过数学归纳法进行证明。

\(3*3\)通过计算机显然成立,我们假设\(x*x\)成立,现在证明\((x+2)*(x+2)\)也成立。

可以这样想,我们现将空格移动到左上角,然后直接用一个\(x*x\)的假想的棋盘将其框住,对数字进行离散化,此时互相可达。然后剩下的棋盘可以被拆分为若干个2*2的,只要将空格移到这些地方,也可以将这些2 * 2的棋盘的每一种可能都玩完而不会影响逆序对数。仔细思考,容易发现离散化是不会影响结果的。

所以我们可以加上逆序对数判无解。

#include<bits/stdc++.h>
using namespace std; 
int cantor(int k[3][3],int len=9){
	int a[15];
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)a[i*3+j+1]=k[i][j];
	int ans=0;
	for(int i=1;i<=len;i++){
		int c=1,m=1,cnt=0;
		for(int j=i+1;j<=len;j++){
			if(a[i]>a[j]){
				++cnt;
			}m*=c;c++;
		}
		ans+=cnt*m;
	} 
	return ans;
}
int jc[10]={1,1,2,6,24,120,720,5040,40320,362880},sx,sy,tx,ty,q1[9],q2[9];
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1},vis[500050][3],dis[1000050][3],S[3][3],T[3][3],s,t;  
bool check(int sx,int sy){
	if(min(sx,sy)<0||max(sx,sy)>2)return false;
	return true;
}
struct node{
	int a[3][3],val,tag,l,r;
};
int bfs(){
	queue<node>q;
	node S1,S2;
	for(int i=0;i<3;i++){
		for(int j=0;j<3;j++){
			S1.a[i][j]=S[i][j];
		}
	}
	for(int i=0;i<3;i++){
		for(int j=0;j<3;j++){
			S2.a[i][j]=T[i][j];
		}
	}
	S1.l=sx,S1.r=sy,S1.tag=1,S1.val=s;
	S2.l=tx,S2.r=ty,S2.tag=0,S2.val=t;
	q.push(S1);q.push(S2);
	dis[s][1]=dis[t][0]=0;vis[s][1]=vis[t][0]=1;
	while(!q.empty()){
		node u=q.front();q.pop();
		for(int i=0;i<4;i++){
			node v;
			int vx=u.l+dx[i],vy=u.r+dy[i];
			if(check(vx,vy)){
				swap(u.a[vx][vy],u.a[u.l][u.r]);
				for(int i=0;i<3;i++){
					for(int j=0;j<3;j++){
						v.a[i][j]=u.a[i][j];
					}
				}
				v.val=cantor(v.a),v.l=vx,v.r=vy;
				swap(u.a[vx][vy],u.a[u.l][u.r]);
				if(vis[v.val][u.tag^1]){
					return dis[v.val][u.tag^1]+1+dis[u.val][u.tag];
				}
				else {
					if(vis[v.val][u.tag])continue;
					vis[v.val][u.tag]=1;
					dis[v.val][u.tag]=dis[u.val][u.tag]+1;
					v.tag=u.tag;
					q.push(v);
				}
			}
		}
	}
	return -1;
}
int main(){
	for(int i=0;i<3;i++){
		for(int j=0;j<3;j++){
			scanf("%d",&S[i][j]);
			q1[i*3+j]=S[i][j];
		}
	}
	int cnt1=0,cnt2=0;
	for(int i=0;i<9;i++){
		for(int j=i+1;j<9;j++){
			if(q1[i]==0||q1[j]==0)continue;
			if(q1[i]>q1[j])cnt1++;
		}
	}
	for(int i=0;i<3;i++){
		for(int j=0;j<3;j++){
			scanf("%d",&T[i][j]);
			q2[i*3+j]=T[i][j];
		}
	}
	for(int i=0;i<9;i++){
		for(int j=i+1;j<9;j++){
			if(q2[i]==0||q2[j]==0)continue;
			if(q2[i]>q2[j])cnt2++;
		}
	}
	if((cnt1+cnt2)&1){
		cout<<"-1\n";
		return 0;
	}
	for(int i=0;i<3;i++)for(int j=0;j<3;j++)if(S[i][j]==0)S[i][j]=9,sx=i,sy=j;
	for(int i=0;i<3;i++)for(int j=0;j<3;j++)if(T[i][j]==0)T[i][j]=9,tx=i,ty=j;
	s=cantor(S),t=cantor(T); 
//	cout<<"X\n";
	cout<<bfs();
}

聪明的打字员

容易发现,操作具有可逆性,且有明确的初始态和终末态,可以考虑进行双向BFS。
HASH可以采用字符串(然后你就被卡了),所以我们采用十进制状压。
然后就是模拟两个操作就行。

int up_down(int num,int pos,int delta){
    int t=num/fz[pos]%10+delta;
    if(t>=0&&t<=9)return num/fz[pos-1]*fz[pos-1]+t*fz[pos]+num%fz[pos];
    return num;
}
int exchange(int num,int pos1,int pos2){
    int lst=num/fz[pos1]%10;
    int now=num/fz[pos2]%10;
    if(lst==now)return num;
    num=num/fz[pos1-1]*fz[pos1-1]+now*fz[pos1]+num%fz[pos1];
    num=num/fz[pos2-1]*fz[pos2-1]+lst*fz[pos2]+num%fz[pos2];
    return num;
}

剩下的就是愉快的套板子啦。哦对了,要写个优化——去重(可以考虑双向A*,估价函数就写两状态中数字之和做差就行,也可以进行排序,然后对相同排名的做差,最后进行求和也行),不然会T(当然可以特判:123456,999999;000000,999999).
完整代码

字串变换

我感觉这题不用说,就是板子题,贴个代码算了。
code

十一迷宫(puzzle)

  1. 状态设计:一个地图表示状态,不可达的点设为12(方便HASH)
  2. 状态哈希:万能的STLmap<string,int>
  3. 状态转移:找到两个空格的位置,分别上下左右转移
  4. 状态剪枝:空格不与空格交换,步数大于10的不入队

完整代码

A-Star

其基于优先队列BFS,其主要思想是\(f(x)=g(x)+h(x)\)

其中\(f(x)\)就是放在优先队列里进行比较的值,\(g(x)\)是当前已经花费的代价,\(h(x)\)是预估未来将要花费的最小代价。

设\(h'(x)\)是状态\(x\)实际花费的代价,则需要满足:

\(h(x)\le h'(x)\),且尽量使得\(|h(x)-h'(x)|\)小。

事实上,优先队列BFS就是\(h(x)\)始终为0的情况。

这时候便可以保证用较快速度得出最优解,感性证明一下得出最优解的正确性:

在最后一步的时候,\(h(x)\)显然只能为0,此时比较的就是\(g(x)\),故不会出错。

\(h(x)\)设计技巧

例题分析

第k短路

骑士精神

排书

引蛇出洞

ID-A-Star

其本质上就是迭代加深+估价函数的DFS。是主要是用来解决A*空间复杂度过大或者不方便HASH,其效率与BFS相当(主要是适当剪枝和常数较小)

例题分析

POSLOZI

涂满它!

破坏正方形

无题

铁盘整理

双向A*

最后我们来探讨一种双向A* 算法(感谢hjl巨佬的启发)。
以下的正确性有待考究,我也未曾在实践中用过,也欢迎HACK。
简而言之,加上估价函数的双向优先队列BFS算法。实测跑得飞起。
算法成立的原因是 A *算法并非一定是到终点才可以结束搜索,只是给搜索路径指引一个正确方向而已。

不过这个算法破坏了原本BFS队列里的三段性,这导致搜索树并不均衡,但只要有优秀的估价函数,开枝散叶也比较少。

代码实现就是多加了个估价函数而已。

标签:return,val,int,高级,笔记,tag,搜索,ans,dis
From: https://www.cnblogs.com/oierpyt/p/17061111.html

相关文章

  • 读书笔记:价值投资.零八.什么是价值投资
     什么叫价值投资如果有人还不熟悉价值投资,那么可以看看巴菲特1984年的演讲:格雷厄姆-多德部落的超级投资者.01.价值投资赚钱的原因价值投资者的投资风......
  • 读编程与类型系统笔记11_高级类型及其他
    1. 范畴论1.1. 范畴论是数学的一个分支,研究的是由对象及这些对象之间的箭头组成的结构1.2. 函子和单子的概念来自范畴论1.3. Haskell是一种编程语言,从范畴论中汲取......
  • sc-cs 笔记20230119
                      ......
  • 《RPC实战与核心原理》学习笔记Day2
    02|协议:怎么设计可扩展且向后兼容的协议?我们为什么需要使用协议?在网络通信的过程中,为了避免语义不一致的情况,我们需要在发送请求时设定一个边界,然后再收到请求时按照......
  • stm32笔记[2]-HAL库驱动2.4寸屏幕
    硬件平台开发板:蓝桥CT117E-M4(DK117E-M4)主控:STM32G431RBT6内置CMSISDAP调试器(STM32F103C8T6)官方例程运行频率:80MHz原理图STM32G4主控STM32F103的CMSIS-DA......
  • 机器学习 吴恩达 第八章 笔记
    八、神经网络的学习(NeuralNetworks:Learning)  在本节,我们学习一个新的神经网络算法.它能在给定训练集的同时,为神经网络拟合参数.与其他算法一样,我们从代价函数开......
  • CSS3学习笔记
    CSS3学习笔记            CSS3选择器总结(表格)html标签有2种关系:包含关系和并列关系;CSS3选择器分类:基本选择器、组合选择器、属性选择器、伪类......
  • 代码随想录day21 LeetCode 530. 二叉搜索树的最小绝对差 501. 二叉搜索树中的众数 236
    530.二叉搜索树的最小绝对差遇到在二叉搜索树上求什么最值,求差值之类的,都要思考一下二叉搜索树可是有序的,要利用好这一特点。classSolution{public:vector<int......
  • 扩欧学习笔记
    扩欧这玩意儿暑假就学了,但是一直仅限于背\(y=\cdots\),背完就忘,于是搞个学习笔记加深印象。解方程\(ax+by=\gcd(a,b)\)设\(ax_1+by_1=\gcd(a,b)\),\(bx_2+(a\modb)y_2=......
  • JavaScript学习笔记—instanceof和hasOwn
    1.instanceof用来检查一个对象是否是一个类的实例检查的是对象的原型链上是否有该类实例(只要原型链上有该类实例,就会返回true)Object是所有对象的原型,所以任何对象和Ob......