第 4 章 广搜的优化技巧

22 篇文章 0 订阅
3 篇文章 0 订阅

例1:电路维修

类型:最短路
题目 //这两题的题面相同

这里写图片描述

题解

边 权 有 0 和 1 两 种 , 所 以 刷 S P F A 。 边权有0和1两种,所以刷SPFA。 01SPFA
S P F A 加 上 优 化 后 才 能 过 , 否 则 会 被 卡 。 SPFA加上优化后才能过,否则会被卡。 SPFA
当 然 , S P F A 这 个 算 法 本 身 也 是 可 以 卡 的 。 当然,SPFA这个算法本身也是可以卡的。 SPFA

代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=505,tt=maxn*maxn;
int n,m,mp[maxn][maxn],dis[maxn][maxn],til,hea,vis[maxn][maxn],INF;
struct js{
	int x,y;
	void gv(int &xx,int &yy){xx=x,yy=y;}
}q[tt];
int gtc()
{
	char ch=getchar();
	while (ch!='\\'&&ch!='/') ch=getchar();
	return ch=='/';
}
bool pd(int x,int y){return x<1||y<1||x>n||y>m;}
void put(int x,int y,int L)
{
	if (dis[x][y]<=L) return;dis[x][y]=L;
	if (vis[x][y]) return;vis[x][y]=1;
	if (++til>=tt) til-=tt;q[til]=(js){x,y};
	int nxt=hea+1;if (nxt>=tt) nxt-=tt;
	int X=q[nxt].x,Y=q[nxt].y;
	if (dis[x][y]<=dis[X][Y]) swap(q[til],q[nxt]);
}
void bfs()
{
	memset(dis,63,sizeof dis);INF=**dis;
	dis[1][1]=0;q[til=1]=(js){1,1};
	while (hea!=til)
	{
		if (++hea>=tt) hea-=tt;
		int x,y,X,Y,p;
		q[hea].gv(x,y);
		
		X=x-1,Y=y-1,p=mp[x-1][y-1];
		if (!pd(X,Y)) put(X,Y,dis[x][y]+p);
		
		X=x+1,Y=y+1,p=mp[x][y];
		if (!pd(X,Y)) put(X,Y,dis[x][y]+p);
		
		X=x+1,Y=y-1,p=mp[x][y-1]^1;
		if (!pd(X,Y)) put(X,Y,dis[x][y]+p);
		
		X=x-1,Y=y+1,p=mp[x-1][y]^1;
		if (!pd(X,Y)) put(X,Y,dis[x][y]+p);
		
		vis[x][y]=0;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;++i)
	 for (int j=1;j<=m;++j) mp[i][j]=gtc();
	++n,++m;bfs();
	if (dis[n][m]!=INF) printf("%d\n",dis[n][m]);else printf("NO SOLUTION");
	return 0;
}

例2:魔板

类型:状态估计
题目

这里写图片描述

题解

事 实 上 只 有 8 ! 种 状 态 , 也 就 是 说 我 们 之 间 B F S 就 可 以 了 。 事实上只有 8! 种状态,也就是说我们之间 BFS 就可以了。 8!BFS

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=10,maxt=40325,maxs=134217728;
#define MOD 10007
#define MOD2 (maxt<<1)
int a[maxn],b[maxn],c[maxn],q[maxt],dis[maxt],lst[maxt],tt,til,hea,end,str,nxt,D;
char ans[maxt];
int ran(){return 1ll*rand()%MOD*rand()%MOD;}
int get(int *a){int sum=0;for (int i=0;i<8;++i) sum=sum<<3|a[i];return sum;}
void put(int sum,int *a){for (int i=7;i>=0;--i) a[i]=sum&7,sum>>=3;}
struct js{
	js* son[2];
	int x,fix;
	js(){son[0]=son[1]=NULL;}
	void cs(){son[0]=son[1]=NULL;}
}*rot=NULL;
js que[maxt<<1];
void rotate(js*&o,int k)
{
	js *u=o->son[k];
	o->son[k]=u->son[k^1];
	u->son[k^1]=o;
	o=u;
}
void insert(js*&o,int x)
{
	if (o==NULL) {++tt;if(tt>MOD2)tt-=MOD2;que[tt].cs();o=&que[tt];o->x=x;o->fix=rand()%MOD;return;}
	int p=x>o->x;
	insert(o->son[p],x);
	if (o->son[p]->fix<o->fix) rotate(o,p);
}
bool fin(js*&o,int x){if (o==NULL) return false;if (o->x==x) return true;return fin(o->son[x>o->x],x);}
void prt(int x)
{
	if (lst[x]) prt(lst[x]),putchar(ans[x]);
}
void bfs()
{
	insert(rot,str);q[++til]=str;int sum;
	if (str==end) {printf("0\n");exit(0);}
	while (hea!=til)
	{
		sum=q[++hea];D=dis[hea]+1;
		put(sum,a);
		if (hea==154){
			int i=10;
		}
		b[0]=a[7];b[1]=a[6];b[2]=a[5];b[3]=a[4];
		b[4]=a[3];b[5]=a[2];b[6]=a[1];b[7]=a[0];
		if (!fin(rot,nxt=get(b)))
		{
			q[++til]=nxt;dis[til]=D;lst[til]=hea;ans[til]='A';
			if (nxt==end) {printf("%d\n",D);prt(til);return;}
			insert(rot,nxt);
		}
		b[0]=a[3];b[1]=a[0];b[2]=a[1];b[3]=a[2];
		b[4]=a[5];b[5]=a[6];b[6]=a[7];b[7]=a[4];
		if (!fin(rot,nxt=get(b)))
		{
			q[++til]=nxt;dis[til]=D;lst[til]=hea;ans[til]='B';
			if (nxt==end) {printf("%d\n",D);prt(til);return;}
			insert(rot,nxt);
		}
		b[0]=a[0];b[1]=a[6];b[2]=a[1];b[3]=a[3];
		b[4]=a[4];b[5]=a[2];b[6]=a[5];b[7]=a[7];
		if (!fin(rot,nxt=get(b)))
		{
			q[++til]=nxt;dis[til]=D;lst[til]=hea;ans[til]='C';
			if (nxt==end) {printf("%d\n",D);prt(til);return;}
			insert(rot,nxt);
		}
	}
}
int main()
{
	tt=til=hea=0;
	srand(time(0));
	for (int i=0;i<8;++i) scanf("%d",c+i),b[i]=i,--c[i];
	str=get(b);end=get(c);
	
	bfs();
	return 0;
}

例3:Knight Moves

类型:模拟
题目

这里写图片描述

题解

小 心 ! 听 说 数 据 范 围 与 题 目 面 不 符 合 , 跳 水 。 小心!听说数据范围与题目面不符合,跳水。

题解
#include<bits/stdc++.h>
using namespace std;
const int maxn=305,f[8][2]={{1,2},{2,1},{-1,2},{2,-1},{1,-2},{-2,1},{-1,-2},{-2,-1}};
int L,x,y,dis[maxn][maxn],til,hea;
struct js{
	int x,y;
}q[maxn*maxn];
bool pd(int x,int y){return x<0||x>L|y<0||y>L||dis[x][y]<1e9;}
void bfs()
{
	for(int a,b,X,Y;hea!=til;)
	{
		a=q[++hea].x;b=q[hea].y;
		for (int i=0;i<8;++i)
		{
			if (!pd(X=a+f[i][0],Y=b+f[i][1]))
			{
				q[++til]=(js){X,Y};
				dis[X][Y]=dis[a][b]+1;
				if (X==x&&Y==y) return;
			}
		}
	}
}
int main()
{
	int T;
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d%d%d",&L,&x,&y);
		for (int i=0;i<=L;++i)
		for (int j=0;j<=L;++j) dis[i][j]=1e9;
		dis[x][y]=hea=0;q[til=1]=(js){x,y};
		scanf("%d%d",&x,&y);bfs();
		printf("%d\n",dis[x][y]);
	}
	return 0;
}

练习1:棋盘游戏

类型:状态设计
题面

这里写图片描述

题解

总 状 态 数 是 很 少 的 , 可 以 全 部 遍 历 一 遍 ( 最 差 的 情 况 ) 总状态数是很少的,可以全部遍历一遍(最差的情况)
但 是 状 态 的 转 移 最 好 直 接 在 二 进 制 下 进 行 ( 把 状 态 看 成 一 个 二 进 制 数 , 直 接 进 行 转 移 ) 但是状态的转移最好直接在二进制下进行(把状态看成一个二进制数,直接进行转移)
这 样 会 快 一 些 这样会快一些
其 实 代 码 中 的 枚 举 , 二 进 制 下 哪 位 为 1 的 循 环 都 可 以 省 略 。 其实代码中的枚举,二进制下哪位为1的循环都可以省略。 1

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=65550;
int a[20],str,end,q[maxn],dis[maxn],til,hea;
int cc()
{
	char c=getchar();
	while (c!='0'&&c!='1') c=getchar();
	return c-'0';
}
int main()
{
	str=end=til=hea=0;
	memset(dis,63,sizeof dis);int INF=*dis;
	for (int i=0;i<16;++i) a[i]=cc();
	for (int i=15;i>=0;--i) str=str<<1|a[i];
	for (int i=0;i<16;++i) a[i]=cc();
	for (int i=15;i>=0;--i) end=end<<1|a[i];
	q[++til]=str;dis[str]=0;
	while (hea!=til)
	{
		int x=q[++hea],y,z,D;D=dis[x]+1;
		for (int i=0;i<4;++i)
		for (int j=0;j<4;++j)
		{
			if (x&1<<(i<<2)+j)
			{
				y=x^1<<(i<<2)+j;
				if (i  &&(z=y^1<<(i-1<<2)+j)>y&&dis[z]>D) dis[z]=D,q[++til]=z;
				if (i<3&&(z=y^1<<(i+1<<2)+j)>y&&dis[z]>D) dis[z]=D,q[++til]=z;
				if (j  &&(z=y^1<<(i<<2)+j-1)>y&&dis[z]>D) dis[z]=D,q[++til]=z;
				if (j<3&&(z=y^1<<(i<<2)+j+1)>y&&dis[z]>D) dis[z]=D,q[++til]=z;
			}
		}
		if (dis[end]!=INF) break;
	}
	printf("%d",dis[end]);
	return 0;
}

练习3:移动玩具

类型:压位
题目

在这里插入图片描述

题解

跳 水 跳水

代码
#include<bits/stdc++.h>
using namespace std;
const int maxs=131075,f[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
int n,str,end,vis[maxs],q[maxs],dis[maxs],hea,til;char s[100];
int gt()
{
	char ch=getchar();while (ch!='0'&&ch!='1') ch=getchar();return ch-'0';
}
int main()
{
	for (int i=0;i<16;++i) str|=gt()<<i;
	for (int i=0;i<16;++i) end|=gt()<<i;
	if (end==str) {printf("0");return 0;}
	memset(dis,63,sizeof dis);dis[str]=hea=til=0;q[++til]=str;
	while (hea!=til)
	{
		int now,otr=0,nxt,x,y,a,b,DIS;
		now=q[++hea];DIS=dis[now]+1;
		for (int i=now&-now;now;i=now&-now)
		{
			int j=log2(i);y=j&3;x=j>>2;now^=i;
			for (int k=0;k<4;++k)
			{
				a=x+f[k][0];b=y+f[k][1];
				if (a<0||b<0||a>3||b>3||(now&1<<(a<<2|b))) continue;
				nxt=now^otr^1<<(a<<2|b);
				if (nxt==end) {printf("%d\n",DIS);return 0;}
				if (dis[nxt]>DIS) dis[q[++til]=nxt]=DIS;
			}
			otr^=i;
		}
	}
	return 0;
}

练习4:山峰和山谷 Ridges and Valleys

类型 BFS
题目

在这里插入图片描述

题解

有 一 种 思 路 是 按 照 每 个 格 子 的 高 度 排 序 , 然 后 从 低 到 高 遍 历 。 有一种思路是按照每个格子的高度排序,然后从低到高遍历。
其 实 完 全 没 必 要 这 样 ( 虽 然 这 样 貌 似 也 能 过 ) , 我 们 只 需 要 每 次 遍 历 相 同 高 度 的 连 通 块 , 如 果 发 现 连 通 块 周 围 存 在 更 高 的 节 点 , 那 么 它 一 定 不 是 山 峰 ( 山 谷 同 理 ) 。 其实完全没必要这样(虽然这样貌似也能过),我们只需要每次遍历相同高度的连通块,如果发现连通块周围存在更高的节点,那么它一定不是山峰(山谷同理)。

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005,f[8][2]={{1,0},{0,1},{-1,0},{0,-1},{1,1},{-1,1},{1,-1},{-1,-1}};
char fil[100000],*A=fil,*B=fil;
int n,m,mp[maxn][maxn],vis[maxn][maxn],anshi,anslo,til,hea;
struct js{
	int x,y,h;
}q[maxn*maxn];;
char gt(){return A==B&&(B=(A=fil)+fread(fil,1,100000,stdin),A==B)?EOF:*A++;}
int rad()
{
	int ret=0,f=1;char ch=gt();
	while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=gt();}
	while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=gt();
	return ret*f;
}

void bfs(int x,int y,int h)
{
	hea=0;q[til=1]=(js){x,y,h};vis[x][y]=1;
	int sf=1,sg=1,X,Y;
	while (hea!=til)
	{
		x=q[++hea].x,y=q[hea].y;h=q[hea].h;
		for (int i=0;i<8;++i)
		{
			X=x+f[i][0];Y=y+f[i][1];
			if (X<1||Y<1||X>n||Y>n) continue;
			if (mp[X][Y]>h) sf=0;
			if (mp[X][Y]<h) sg=0;
			if (mp[X][Y]!=h||vis[X][Y]) continue;
			q[++til]=(js){X,Y,mp[X][Y]};vis[X][Y]=1;
		}
	}
	anshi+=sf;anslo+=sg;
}
int main()
{
	n=rad();m=0;
	for (int i=1;i<=n;++i)
	 for (int j=1;j<=n;++j) mp[i][j]=rad();
	for (int i=1;i<=n;++i)
	 for (int j=1;j<=n;++j) if (!vis[i][j]) bfs(i,j,mp[i][j]);;
	printf("%d %d",anshi,anslo);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值