0
点赞
收藏
分享

微信扫一扫

[周专题系列]kuangbin 专题1 简单搜索

艾晓雪 2022-11-25 阅读 76


[周专题系列]kuangbin 专题1 简单搜索

​​题单链接​​

知识点

题目

类型

棋盘问题

dfs回溯八皇后变形

Dungeon Master

3D BFS求最短路

Catch That Cow

一维BFS最短路,估计搜索范围,BFS缩小数据范围

Fliptile

选与不选,二进制枚举,输出字典序最小

Find The Multiple

BFS找最小的,缩小范围

Prime Path

BFS搜索缩小答案范围直接输出

Shuffle’m Up

模拟

POTS

BFS利用string记录最短路径

Fire Game

多源点BFS

Fire!

两个物体的BFS

迷宫问题

BFS记录路径

Oil Deposits

FLood-Fill

非常可乐

模拟/数论

Find a Way

两次BFS

题目

棋盘问题

思路
八皇后变形,区别是不用每行都放满,其他差不多
每一行有选和不选两种,然后枚举选第几列
dfs状态参数(对第几行作选择,已经放了多少个)
不需要把列也作为参数,col数组已经记录了
代码

#include<bits/stdc++.h>
using namespace std;
const int N=10;
char g[N][N];
bool col[N];
int n,k,ans;
void dfs(int x,int t)
{
if(t==k)
{
ans++;
return;
}
if(x==n)return;
for(int i=0;i<n;i++)
{
if(col[i]||g[x][i]!='#')continue;
col[i]=1;
dfs(x+1,t+1);
col[i]=0;
}
dfs(x+1,t);
}
int main()
{
while(~scanf("%d%d",&n,&k))
{
if(n==-1&&k==-1)break;
for(int i=0;i<n;i++)scanf("%s",g[i]);
ans=0;
dfs(0,0);
printf("%d\n",ans);
}
return 0;
}

Dungeon Master

思路
3维度BFS最短路,比较容易。
需要注意的是,POJ C++98标准还不支持结构体{},交G++就可以了
代码

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=35;
int L,R,C;
int sx,sy,sz,ex,ey,ez;
bool flag;
char g[N][N][N];
bool st[N][N][N];
int dist[N][N][N];
struct Node
{
int z,x,y;
};
int dx[]={1,-1,0,0,0,0};
int dy[]={0,0,1,-1,0,0};
int dz[]={0,0,0,0,1,-1};
void bfs(int sz,int sx,int sy)
{
memset(dist,0x3f,sizeof dist);
Node input;
input.z=sz,input.x=sx,input.y=sy;
queue<Node>q;
q.push(input);
st[sz][sx][sy]=1;
dist[sz][sx][sy]=0;
while(q.size())
{
Node t=q.front();
if(t.z==ez&&t.x==ex&&t.y==ey)
{
flag=1;
break;
}
q.pop();
for(int i=0;i<6;i++)
{
int a=t.z+dz[i];
int b=t.x+dx[i];
int c=t.y+dy[i];
if(a<0||b<0||c<0||a>=L||b>=R||c>=C)continue;
if(st[a][b][c]||g[a][b][c]=='#')continue;
st[a][b][c]=1;
Node tmp;
tmp.z=a,tmp.x=b,tmp.y=c;
q.push(tmp);
dist[a][b][c]=dist[t.z][t.x][t.y]+1;
}
}
}
int main()
{
while(~scanf("%d%d%d",&L,&R,&C)&&(L||R||C))
{
for(int i=0;i<L;i++)
for(int j=0;j<R;j++)
scanf("%s",g[i][j]);


for(int i=0;i<L;i++)
for(int j=0;j<R;j++)
for(int k=0;k<C;k++)
{
if(g[i][j][k]=='S')sz=i,sx=j,sy=k;
if(g[i][j][k]=='E')ez=i,ex=j,ey=k;
}
memset(st,0,sizeof st);
flag=0;
bfs(sz,sx,sy);
if(flag) printf("Escaped in %d minute(s).\n",dist[ez][ex][ey]);
else puts("Trapped!");
}
return 0;
}

Catch That Cow

思路
acwing题单里刷到过了
​抓住那头牛​​代码
直接看那篇博客里就行,不过POJ交的话要把万能头改一下

Fliptile

思路
选与不选(二进制枚举),类似题目是蓝书上费解的开关
有以下性质:
1:每个点最多点一次
2:如果第一行已经固定,那么满足题意的点击方案最多只有一种。因为当第i行某位为1时,若前i行已经被固定,那么只能点击第i+1行该位置上的数字才能使第i行变为0,归纳后可以得到此结论(所以我们只要枚举第一行方案即可)
3:点击的先后顺序不影响结果(做搜索的时候要确定操作选择顺序是否会影响答案)

这题要求最小操作数并输出字典序最小的,那么我们可以把输出的二维数组压缩为string字符串,如果当前方案数量和已有最小数量相等比较ans和当前方案的字符串并更新

代码

#include<algorithm>
#include<iostream>
#include<string>
using namespace std;

const int N=20;
bool g[N][N],tmp[N][N];
int cnt[N][N];
string ans;
int n,m;
void change(int x,int y)//翻转模块
{
tmp[x][y]=!tmp[x][y];
if(x-1>=0)tmp[x-1][y]=!tmp[x-1][y];
if(x+1<n)tmp[x+1][y]=!tmp[x+1][y];
if(y-1>=0)tmp[x][y-1]=!tmp[x][y-1];
if(y+1<m)tmp[x][y+1]=!tmp[x][y+1];
}
void init()//初始化
{
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
tmp[i][j]=g[i][j];
}

bool check()//检查是否可以
{
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(tmp[i][j])return 0;
return 1;
}

int main()
{
while(~scanf("%d%d",&n,&m))
{
ans="";
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
scanf("%d",&g[i][j]);
tmp[i][j]=g[i][j];
}
int min_count=0x3f3f3f3f;
for(int i=0;i<(1<<m);i++)//枚举第一行方案
{
init();
int count=0;
string str="";
for(int j=0;j<m;j++)
if(i&(1<<j))
{
change(0,j);
str+='1';
count++;
}
else str+='0';
for(int j=1;j<n;j++)
for(int k=0;k<m;k++)
if(tmp[j-1][k])
{
change(j,k);
str+='1';
count++;
}
else str+='0';
if(check())//更新答案
{
if(count<min_count)
{
min_count=count;
ans=str;
}
else if(count==min_count)//字典序比较更新
{
if(str<ans)
{
ans=str;
printf("new ans is ID=%d\n",i);
}
}
}
}
if(min_count!=0x3f3f3f3f)//输出答案
{
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
printf("%c ",ans[i*m+j]);
}
puts("");
}
}
else puts("IMPOSSIBLE");
}
return 0;
}

Find The Multiple

思路
找一个>=n的倍数m,且只由0,1构成,bfs搜索然后迷之MLE
看别人是c++ stl queue TLE
索性直接打表

原本有顾虑担心会超过long long,但是bfs不会,很小的。所以这种要大胆搞啊!说不定范围很小
代码
打表代码

#include<iostream>
#include<queue>
using namespace std;
typedef long long ll;
ll n;
queue<ll>q;
ll bfs()
{
q.push(1);
while(!q.empty())
{
ll t=q.front();
q.pop();
if(t%n==0&&t>=n)return t;
q.push(t*10);
q.push(t*10+1);
}
return q.front();
}
int main()
{
while(~scanf("%lld",&n)&&n)
{
printf("%lld\n",bfs());
}
return 0;
}

Prime Path

思路
A,B为四位数
找到A-B的素数路径,每次换掉四位数中一个数并保证一直是素数。首位不能为0。找出最短路径
线性筛预处理,BFS求解即可

代码

#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>

using namespace std;

const int N=1e5+10;
int prime[N],cnt;
bool st[N],used[N];
struct Node
{
int x,s;
};
void get_prime(int n)
{
st[0]=st[1]=1;
for(int i=2;i<=n;i++)
{
if(!st[i])prime[++cnt]=i;
for(int j=1;prime[j]<=n/i;j++)
{
st[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}

int find_path(int a,int b)
{
Node tmp;
tmp.x=a,tmp.s=0;
queue<Node>q;
q.push(tmp);
used[a]=1;
while(!q.empty())
{
Node t=q.front();
q.pop();
if(t.x==b)return t.s;
for(int i=0;i<=9;i++)
{
int x1=t.x-t.x/1000*1000+1000*i;//1
int x2=t.x-(t.x/100%10)*100+100*i;//2
int x3=t.x-(t.x/10%10)*10+10*i;//3
int x4=t.x-t.x%10+i;//4
if(x1>=1000&&!st[x1]&&!used[x1])
{
tmp.x=x1;tmp.s=t.s+1;
q.push(tmp);
used[x1]=1;
}
if(!st[x2]&&!used[x2])
{
tmp.x=x2;tmp.s=t.s+1;
q.push(tmp);
used[x2]=1;
}
if(!st[x3]&&!used[x3])
{
tmp.x=x3;tmp.s=t.s+1;
q.push(tmp);
used[x3]=1;
}
if(!st[x4]&&!used[x4])
{
tmp.x=x4;tmp.s=t.s+1;
q.push(tmp);
used[x4]=1;
}
}
}
return -1;

}
int main()
{
get_prime(N);
int T,a,b;
scanf("%d",&T);
while(T--)
{
memset(used,0,sizeof(used));
scanf("%d%d",&a,&b);
int ans=find_path(a,b);
if(ans==-1)puts("Impossible");
else printf("%d\n",ans);
}
return 0;
}

Shuffle’m Up

思路
暴力模拟就完事了
代码

#include<iostream>
#include<algorithm>
#include<queue>
#include<string>
#include<map>

using namespace std;

map<string,int>mp;
int T,n;

int bfs(string a,string b,string c)
{
queue<string>q;
string A,B,C;
int cnt=0;
q.push(a);q.push(b);
while(!q.empty())
{
if(q.size()==1)
{
C=q.front();
if(C==c)return cnt;
A=B="";
q.pop();
for(int i=0;i<n;i++)A+=C[i];
for(int i=0;i<n;i++)B+=C[i+n];
q.push(A);q.push(B);
}
else if(q.size()==2)
{
A=q.front();q.pop();
B=q.front();q.pop();
C="";
for(int i=0;i<n;i++)C=C+B[i]+A[i];
if(mp.count(C)==0)
{
mp[C]++;
q.push(C);
cnt++;
}
else return -1;
}
}
return -1;
}
int main()
{
scanf("%d",&T);
for(int i=1;i<=T;i++)
{
string a,b,c;
scanf("%d",&n);
cin>>a>>b>>c;
mp.clear();
printf("%d %d\n",i,bfs(a,b,c));
}
return 0;
}

Pots

思路
模块化编程&广搜记忆路径
我利用string来记录感觉比较方便
代码

#include<iostream>
#include<string>
#include<cstring>
#include<queue>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int N=1e6+10;
string Path[]={"FILL(1)","FILL(2)","POUR(1,2)","POUR(2,1)","DROP(1)","DROP(2)"};
struct Node
{
int a,b,s;
string p;
};
int A,B,C;
queue<Node>q;
bool st[N];
void F(int x,Node t)
{
if(x==1&&!st[A*1000+t.b])q.push({A,t.b,t.s+1,t.p+'0'}),st[A*1000+t.b]=1;
else if(x==2&&!st[t.a*1000+B]) q.push({t.a,B,t.s+1,t.p+'1'}),st[t.a*1000+B]=1;
}
void P(int x,int y,Node t)
{
if(x==1&&y==2)
{
if(t.a+t.b>=B&&!st[(t.a+t.b-B)*1000+B])q.push({t.a+t.b-B,B,t.s+1,t.p+'2'}),st[(t.a+t.b-B)*1000+B]=1;
else if(t.a+t.b<B&&!st[(t.a+t.b)]) q.push({0,t.a+t.b,t.s+1,t.p+'2'}),st[(t.a+t.b)]=1;
}
else
{
if(t.a+t.b>=A&&!st[A*1000+t.a+t.b-A])q.push({A,t.a+t.b-A,t.s+1,t.p+'3'}),st[A*1000+t.a+t.b-A]=1;
else if(t.a+t.b<A&&!st[(t.a+t.b)*1000])q.push({t.a+t.b,0,t.s+1,t.p+'3'}),st[(t.a+t.b)*1000]=1;
}


}
void D(int x,Node t)
{
if(x==1&&!st[t.b])q.push({0,t.b,t.s+1,t.p+'4'}),st[t.b]=1;
else if(x==2&&!st[t.a*1000]) q.push({t.a,0,t.s+1,t.p+'5'}),st[t.a*1000]=1;
}

void bfs()
{
q.push({0,0,0,""});
st[0]=1;
while(q.size())
{
Node t=q.front();
if(t.a==C||t.b==C)
{
printf("%d\n",t.s);
string ans=t.p;
for(int i=0;i<t.s;i++)cout<<Path[ans[i]-'0']<<endl;
return;
}
q.pop();
F(1,t);F(2,t);
P(1,2,t);P(2,1,t);
D(1,t);D(2,t);
}
printf("impossible");
return;
}

int main()
{
cin>>A>>B>>C;
bfs();
return 0;
}

Fire Game

思路
双起点,四重循环枚举起点
代码

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;

const int N=12,INF=0x3f3f3f3f;
char g[N][N];
int dist[N][N];
int st[N][N];
int n,m,T;
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
struct Node
{
int x,y;
};
int bfs(int x1,int y1,int x2,int y2)
{
queue<Node>q;
Node tmp;
for(int i=0;i<n;i++)//初始化
for(int j=0;j<m;j++)
dist[i][j]=INF;
tmp.x=x1;tmp.y=y1;q.push(tmp);
tmp.x=x2;tmp.y=y2;q.push(tmp);//插入两个起点
dist[x1][y1]=dist[x2][y2]=0;
st[x1][y1]++;st[x2][y2]++;
while(q.size())
{
Node t=q.front();
q.pop();
for(int i=0;i<4;i++)
{
int a=t.x+dx[i],b=t.y+dy[i];
if(a<0||b<0||a>=n||b>=m)continue;
if(st[a][b]==2||g[a][b]!='#')continue;
tmp.x=a;tmp.y=b;q.push(tmp);
st[a][b]++;
dist[a][b]=min(dist[a][b],dist[t.x][t.y]+1);
}
}
int res=0;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(g[i][j]=='#')
{
if(dist[i][j]==0x3f3f3f3f)return -1;
res=max(res,dist[i][j]);
}
return res;
}
int main()
{
cin>>T;
for(int C=1;C<=T;C++)
{
cin>>n>>m;
for(int i=0;i<n;i++)scanf("%s",g[i]);
int ans=INF;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
for(int p=0;p<n;p++)
for(int q=0;q<m;q++)//枚举双起点
if(g[i][j]=='#'&&g[p][q]=='#')
{
memset(st,0,sizeof(st));
int d=bfs(i,j,p,q);
if(d!=-1)ans=min(ans,d);
}

if(ans==INF)ans=-1;
cout<<"Case "<<C<<": "<<ans<<endl;
}
return 0;
}

Fire

思路
两种操作
一种先把Fire全部蔓延预处理出每个点到火的最短距离(注意有多个火的源点)然后再让Joe BFS一遍,必须距离小于火到这里的距离才可以(然后我写裂开了呜呜呜)
另外一种思路是同时走,同一秒内人先走然后火再蔓延(可以用Flood-fill把蔓延的变成障碍)
下面是其他大佬的代码:​​uva Fire​​代码

#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 1005;
int t, n, m, vis[N][N];
char g[N][N];
int dx[4] = {1, 0, 0, -1};
int dy[4] = {0, 1, -1, 0};
struct node {
int x, y;
node(int x, int y): x(x), y(y){}
};
void bfs() {
queue<node> jq; //J所在的点的队列
queue<node> fq; //着火点的队列
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (g[i][j] == 'J') {
jq.push(node(i, j));
vis[i][j] = 1;
} else if (g[i][j] == 'F') {
fq.push(node(i, j));
g[i][j] = '#';//代表不可走
}
}
}
int step = 0;
int cnt= 0;
while (jq.size()) {
step++; //每秒中更新队列里面的所有点
//J先跑 火再蔓延
for (int i = 0, j = jq.size(); i < j; i++) {
node t = jq.front();
jq.pop();
//表明这个点被火给蔓延了不能走了 因为是人先走,火再蔓延
if (g[t.x][t.y] == '#') continue;
for (int k = 0; k < 4; k++) {
int fx = t.x + dx[k];
int fy = t.y + dy[k];
if (fx >= 0 && fy >= 0 && fx < n &&fy < m) {
//表明下个点可走入队
if (g[fx][fy] != '#' && !vis[fx][fy]) {
jq.push(node(fx, fy));
vis[fx][fy] = 1;
}
} else {
//代表走出了边界
printf("%d\n", step);
return;
}
}
}
//火蔓延
for (int i = 0, j = fq.size(); i < j; i++) {
node t = fq.front();
fq.pop();
for (int k = 0; k < 4; k++) {
int fx = t.x + dx[k];
int fy = t.y + dy[k];
if (fx >= 0 && fy >= 0 && fx < n && fy < m && g[fx][fy] != '#') {
fq.push(node(fx, fy));
g[fx][fy] = '#';
}
}
}
}
printf("IMPOSSIBLE\n");
}

int main() {
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) {
scanf("%s", g[i]);
}
bfs();
memset(vis, 0, sizeof vis);
}
return 0;
}

迷宫问题

思路

代码

#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<stdio.h>
using namespace std;

const int N=5;
int g[N][N];
struct Node
{
int x,y;
string s;
};
bool st[N][N];
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
void bfs()
{
queue<Node>q;
q.push({0,0,"00"});
st[0][0]=1;
while(q.size())
{
Node t=q.front();
q.pop();
if(t.x==4&&t.y==4)
{
int len=t.s.size();
for(int i=0;i<len;i=i+2)
{
printf("(%c, %c)\n",t.s[i],t.s[i+1]);
}
return;
}
for(int i=0;i<4;i++)
{
int a=t.x+dx[i],b=t.y+dy[i];
if(a<0||b<0||a>=5||b>=5)continue;
if(g[a][b]==1||st[a][b])continue;
char c='0'+a;
string p=t.s+c;
c='0'+b;p+=c;
q.push({a,b,p});
st[a][b]=1;
}
}
}
int main()
{
for(int i=0;i<5;i++)
for(int j=0;j<5;j++)
scanf("%d",&g[i][j]);
bfs();

return 0;
}

Oil Deposits

思路
Flood-FILL算法
代码

#include<queue>
#include<iostream>
#include<string>
#include<cstring>

using namespace std;

const int N=105;
char g[N][N];
int n,m;
int dx[]={1,1,1,-1,-1,-1,0,0};
int dy[]={0,1,-1,0,1,-1,1,-1};
void dfs(int x,int y)
{
for(int i=0;i<8;i++)
{
int a=x+dx[i],b=y+dy[i];
if(a<0||b<0||a>=n||b>=m||g[a][b]!='@')continue;
g[a][b]='*';
dfs(a,b);
}

return;
}
int main()
{
while(~scanf("%d%d",&n,&m)&&(n||m))
{
for(int i=0;i<n;i++)scanf("%s",&g[i]);
int cnt=0;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(g[i][j]=='@')
{
g[i][j]='*';
dfs(i,j);
cnt++;
}
cout<<cnt<<endl;
}

return 0;
}

非常可乐

思路
BFS加模拟和原本POTS基本差不多但是看到别的人说可以找规律那就换一种写法(其实是懒得写模拟hhh)
​很nb的证明​​

代码

#include<iostream>
using namespace std;
int a,b,c;
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
int main()
{
while(~scanf("%d%d%d",&a,&b,&c)&&(a||b||c))
{
a/=gcd(b,c);
if(a&1)puts("NO");
else printf("%d\n",a-1);
}
return 0;
}

Find a way

思路
两次BFS即可,注意要都能访问到的才更新答案
代码

#include<iostream>
#include<queue>
#include<cstring>
#include<stdio.h>
using namespace std;
const int N=205;
char g[N][N];
bool st[N][N];
int vis[N][N];
int dist[N][N];
int n,m;
struct Node
{
int x,y;
};
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
void bfs(int sx,int sy)
{
queue<Node>q;
memset(st,0,sizeof(st));
q.push({sx,sy});
st[sx][sy]=1;
vis[sx][sy]++;
while(q.size())
{
Node t=q.front();
q.pop();
for(int i=0;i<4;i++)
{
int a=t.x+dx[i],b=t.y+dy[i];
if(a<0||b<0||a>=n||b>=m||st[a][b]||g[a][b]=='#')continue;
q.push({a,b});
dist[a][b]+=dist[t.x][t.y]+11;
vis[a][b]++;
st[a][b]=1;
}
}
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(g[i][j]!='@')dist[i][j]=0;

}
int main()
{
while(~scanf("%d%d",&n,&m))
{
memset(dist,0,sizeof(dist));
memset(vis,0,sizeof(vis));
for(int i=0;i<n;i++)scanf("%s",g[i]);
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(g[i][j]=='Y'||g[i][j]=='M')
bfs(i,j);
int ans=0x3f3f3f3f;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(g[i][j]=='@'&&vis[i][j]==2)ans=min(ans,dist[i][j]);
printf("%d\n",ans);
}
return 0;
}

总结

BFS主要这几个题型:

  1. 最短路+模拟
  2. Flood-Fill
  3. 最短路缩小答案范围
  4. 多源点BFS
  5. 多物体BFS


举报

相关推荐

0 条评论