[周专题系列]kuangbin 专题1 简单搜索
作者:互联网
[周专题系列]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主要这几个题型:
- 最短路+模拟
- Flood-Fill
- 最短路缩小答案范围
- 多源点BFS
- 多物体BFS
标签:tmp,专题,int,st,搜索,&&,kuangbin,push,include 来源: https://blog.csdn.net/qq_39354847/article/details/114378244