基础确实不扎实…拿了3天的时间写了一下kuangbin的第一个专题,在此自己总结一下,讲几道记忆深刻的题,首先记住,搜索问题中对于状态的抽离与遍历非常重要!
1.POJ 2251 Dungeon Master
与一般bfs的区别不大,就一个3维地图,6个方向,
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
int d[6][3]={{-1,0,0},{1,0,0},{0,-1,0},{0,1,0},{0,0,1},{0,0,-1}};//记录方向移动带来的变化
char map[35][35][35];//层 行 列
bool vis[35][35][35];
int l,r,c,sx,sy,sz,ex,ey,ez;
struct node{
int x,y,z,step;
};
bool check(int x,int y,int z)
{
if(x<0 || y<0 || z<0 || x>=l || y>=r || z>=c)
return 1;
else if(map[x][y][z] == '#')
return 1;
else if(vis[x][y][z])
return 1;
return 0;
}
int bfs()
{
queue<node>q;
node now,next;
now.x=sx,now.y=sy,now.z=sz,now.step=0;
q.push(now),vis[now.x][now.y][now.z]=1;
while(q.size())
{
now=q.front();q.pop();
if(now.x==ex&&now.y==ey&&now.z==ez)return now.step;
for(int i=0;i<6;++i)
{
next=now;
next.x=now.x+d[i][0];
next.y=now.y+d[i][1];
next.z=now.z+d[i][2];
if(check(next.x,next.y,next.z))
continue;
vis[next.x][next.y][next.z] = 1;
next.step = now.step+1;
q.push(next);
}
}
return 0;
}
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",map[i][j]);
for(int k=0;k<c;++k)
{
if(map[i][j][k]=='S')//记录起点
{
sx=i,sy=j,sz=k;
}
else if(map[i][j][k]=='E')
{
ex=i,ey=j,ez=k;
} //记录终点
}
}
}
memset(vis,0,sizeof(vis));//重新初始化
int ans;
ans = bfs();
if(ans)
printf("Escaped in %d minute(s).\n",ans);
else
printf("Trapped!\n");
}
return 0;
}
2.C - Catch That Cow POJ - 3278
也是个裸的bfs,每个节点就3种状态 +1,-1,*2,目标位置小于的时候直接输出差值,这里我自己要注意的是,三个状态是并列的,每次取出来,一定要记住当前节点和往下的节点,三个都得跑一遍。
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int maxn=1e5+10;
int d[maxn],n,k;bool v[maxn];
void bfs()
{
queue<int>q;
v[n]=1,d[n]=0,q.push(n);
while(!q.empty())
{
int x=q.front();q.pop();
if(x==k){printf("%d\n",d[k]);return;}
int next;
next=x+1;
if(next<=100000&&next>=0&&!v[next])
{
d[next]=d[x]+1;
v[next]=1;
q.push(next);
}
next=x-1;
if(next<=100000&&next>=0&&!v[next])
{
d[next]=d[x]+1;
v[next]=1;
q.push(next);
}
next=2*x;
if(next<=100000&&next>=0&&!v[next])
{
d[next]=d[x]+1;
v[next]=1;
q.push(next);
}
}
}
int main()
{
while(~scanf("%d%d",&n,&k))
{
if(n>k)printf("%d\n",n-k);
else bfs();
}
}
3.D - Fliptile POJ - 3279
二进制状态搜索,当第一行决定了接下来的按键方法也就确定了。学会了二进制枚举的方法,n位是0~2^n-1,由于逆字典序,要反向枚举,取出第k位是i>>k&1
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=0x7FFFFFFF;
///Map存放棋盘,turn存放每个棋子(地板)翻转的状态(翻转几次)
///ans存放最后要输出的每个地板翻转的次数,cnt记录翻转地板的总次数
///res存放最小的翻转次数,M代表行数,N代表列数
int map[17][17],turn[17][17],ans[17][17],cnt,res,M,N;
int dir[5][2]={{0,0},{0,1},{1,0},{0,-1},{-1,0}};
int getcolor(int x,int y)
{
int temp=map[x][y];
for(int i=0;i<5;++i)
{
int xi=x+dir[i][0];
int yi=y+dir[i][1];
if(xi>=0&&xi<M&&yi>=0&&yi<N)temp+=turn[xi][yi];
}
return temp%2;
}
void dfs()
{
for(int i=1;i<M;++i)
{
for(int j=0;j<N;++j)
{
if(getcolor(i-1,j))turn[i][j]=1,cnt++;
if(cnt>res)return;
}
}
for(int j=0;j<N;++j)
if(getcolor(M-1,j))return;
if(cnt<res)memcpy(ans,turn,sizeof(turn)),res=cnt;
}
int main()
{
while(~scanf("%d%d",&M,&N))
{
res=maxn;
for(int i=0;i<M;++i)
for(int j=0;j<N;++j)
scanf("%d",&map[i][j]);
for(int i=0;i<(1<<N);++i)
{
cnt=0;
memset(turn,0,sizeof(turn));
for(int j=0;j<N;++j)
{
turn[0][N-j-1]=i>>j&1;
if(turn[0][N-j-1])cnt++;
}
dfs();
}
if(res==maxn)printf("IMPOSSIBLE\n");
else
{
for(int i=0;i<M;++i)
{
for(int j=0;j<N;++j)
{
if(j>0)printf(" ");
printf("%d",ans[i][j]);
}
putchar('\n');
}
}
}
return 0;
}
E - Prime Path POJ - 3126
这题一开始我打表枚否质数判断是否只更新了一个数,但是这样复杂度太高了,看了网上题解才知道只要枚举4个位的数字就好了,学到了学到了,这样bfs转移充其量是40n的复杂度。
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
int prime[1000],d[10000],cnt=0,sx,ex;
bool v[10002],vis[10000];
using namespace std;
void primes()
{
v[1]=v[0]=1;
for(int i=2;i<=10000;++i)
{
if(!v[i])prime[++cnt]=i;
for(int j=1;prime[j]*i<=10000&&j<=cnt;++j)
{
v[prime[j]*i]=1;
if(i%prime[j]==0)
break;
}
}
}
bool check(int e)
{
if(v[e])return 0;
if(vis[e])return 0;
return 1;
}
int bfs()
{
queue<int>q;
q.push(sx),d[sx]=0,vis[sx]=1;
int num,y;
while(q.size())
{
int x=q.front();q.pop();
if(x==ex){return d[x];}
int t[5];
t[1]=x/1000; ///记录千位数
t[2]=x/100%10; ///记录百位数
t[3]=x/10%10; ///记录十位
t[4]=x%10; ///记录各位
for(int i=1;i<=4;++i)
{
int temp=t[i];
for(int j=0;j<10;++j)
{
if(t[i]!=j)
{
t[i]=j;
num=t[1]*1000+t[2]*100+t[3]*10+t[4];
}
if(num>=1000&&num<=9999&&check(num))
{
q.push(num);
d[num]=d[x]+1;
vis[num]=1;
}
}
t[i]=temp;
}
}
return -1;
}
int main()
{
primes();
int n;
scanf("%d",&n);
while(n--)
{
memset(vis,0,sizeof(vis));
memset(d,0,sizeof(d));
scanf("%d%d",&sx,&ex);
int ans=bfs();
if(ans!=-1)
{
printf("%d\n",ans);
}
else printf("Impossible\n");
}
return 0;
}
G - Pots POJ - 3414
经典BFS倒水问题,关键是分清从一个节点转移到另一个节点有几种状态,通过这题还学会了记录路径,通过数字表示不同路径,每进一层路径++,由于每一个节点的父亲节点是唯一的。对于每个节点记录他自己的路径,所以开一个char[]
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int MAXN = 100;
int a, b, c;
bool vis[MAXN+1][MAXN+1];
struct node {
int a, b, level;
char path[MAXN+1];
int plen;
};
string path[] = {
"FILL(1)"
,"FILL(2)"
,"DROP(1)"
,"DROP(2)"
,"POUR(1,2)"
,"POUR(2,1)"
};
void output_result(int lvl, char p[], int n)
{
cout << lvl << endl;
for(int i=0; i<n; i++)
cout << path[(int)p[i]] << endl;
}
void bfs()
{
queue<node> q;
memset(vis, true, sizeof(vis));
node f;
f.a = 0;
f.b = 0;
f.level = 0;
memset(f.path, 0, sizeof(f.path));
f.plen = 0;
q.push(f);
vis[f.a][f.b] = false;
while(!q.empty()) {
f = q.front();
q.pop();
if(f.a == c || f.b == c) {
output_result(f.level, f.path, f.plen);
return;
}
node v;
v = f;
v.level++;
v.plen++;
// FILL(a)
if(a - f.a > 0) {
v.a = a;
v.b = f.b;
if(vis[v.a][v.b]) {
v.path[f.plen] = 0;
q.push(v);
vis[v.a][v.b] = false;
}
}
// FILL(b)
if(b - f.b > 0) {
v.a = f.a;
v.b = b;
if(vis[v.a][v.b]) {
v.path[f.plen] = 1;
q.push(v);
vis[v.a][v.b] = false;
}
}
// DROP(a)
if(f.a) {
v.a = 0;
v.b = f.b;
if(vis[v.a][v.b]) {
v.path[f.plen] = 2;
q.push(v);
vis[v.a][v.b] = false;
}
}
// DROP(b)
if(f.b) {
v.a = f.a;
v.b = 0;
if(vis[v.a][v.b]) {
v.path[f.plen] = 3;
q.push(v);
vis[v.a][v.b] = false;
}
}
// POUR(a,b)
if(f.a && (f.b < b)) {
if(f.a > (b - f.b)) {
v.a = f.a -(b - f.b);
v.b = b;
} else {
v.a = 0;
v.b = f.b + f.a;
}
if(vis[v.a][v.b]) {
v.path[f.plen] = 4;
q.push(v);
vis[v.a][v.b] = false;
}
}
// POUR(b,a)
if(f.b && (f.a < a)) {
if(f.b > (a - f.a)) {
v.a = a;
v.b = f.b -(a - f.a);
} else {
v.a = f.a + f.b;
v.b = 0;
}
if(vis[v.a][v.b]) {
v.path[f.plen] = 5;
q.push(v);
vis[v.a][v.b] = false;
}
}
}
cout << "impossible" << endl;
}
int main()
{
cin >> a >> b >> c;
bfs();
return 0;
}
H - Fire! UVA - 11624
双重BFS,所有的火都是同一层树下,用time数组存储预处理出火跑的位置加入判断就好了
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int MAXN = 1005;
const int INF = 0x3f3f3f3f;
const int dx[] = {-1, 0, 0, 1};
const int dy[] = {0, -1, 1, 0};
struct node{
node (int xx, int yy, int dd) {
x = xx;
y = yy;
d = dd;
}
int x, y, d;
};
char g[MAXN][MAXN];
int vf[MAXN][MAXN], vm[MAXN][MAXN], T[MAXN][MAXN];
int r, c, sx, sy;
queue<node> qf;
void bfs_fire() {
while (!qf.empty()) {
node u = qf.front();
qf.pop();
node v = u;
for (int i = 0; i < 4; i++) {
int tx = u.x + dx[i];
int ty = u.y + dy[i];
if (tx < 0 || tx >= r || ty < 0 || ty >= c || g[tx][ty] == '#') continue;
if (!vf[tx][ty]) {
vf[tx][ty] = 1;
v.x = tx;
v.y = ty;
v.d = u.d + 1;
T[tx][ty] = v.d;
qf.push(v);
}
}
}
}
int bfs_man() {
queue<node> qm;
while (!qm.empty()) qm.pop();
node s(sx, sy, 0);
qm.push(s);
memset(vm, 0, sizeof(vm));
vm[sx][sy] = 1;
while (!qm.empty()) {
node u = qm.front();
qm.pop();
if (u.x == 0 || u.x == r - 1 || u.y == 0 || u.y == c - 1)
return u.d + 1;
node v = u;
for (int i = 0; i < 4; i++) {
int tx = u.x + dx[i];
int ty = u.y + dy[i];
if (tx < 0 || tx >= r || ty < 0 || ty >= c || g[tx][ty] == '#') continue;
if (u.d + 1 >= T[tx][ty]) continue;
if (!vm[tx][ty]) {
vm[tx][ty] = 1;
v.x = tx;
v.y = ty;
v.d = u.d + 1;
qm.push(v);
}
}
}
return -1;
}
int main() {
int cas;
scanf("%d", &cas);
while (cas--) {
scanf("%d%d", &r, &c);
for (int i = 0; i < r; i++)
scanf("%s", g[i]);
while(!qf.empty()) qf.pop();
memset(vf, 0, sizeof(vf));
memset(T, INF, sizeof(T));
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
if (g[i][j] == 'J') {
sx = i;
sy = j;
}
if (g[i][j] == 'F') {
node tmp(i, j, 0);
vf[i][j] = 1;
T[i][j] = 0;
qf.push(tmp);
}
}
}
bfs_fire();
int ans = bfs_man();
if (ans == -1)
printf("IMPOSSIBLE\n");
else
printf("%d\n", ans);
}
return 0;
}
I - 迷宫问题 POJ - 3984
典型BFS,学到了回溯的时候用结点类型pre数组记录每一个节点的上一个节点的编号,从而回溯
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
struct node
{
int x,y;
};
node pre[6][6];
int map[6][6];int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}},vis[6][6];
void print(node a) //pre数组存储前一个结点递归回溯输出路径
{
if(a.x==0&&a.y==0)
{
printf("(0, 0)\n");
return;
}
print(pre[a.x][a.y]);
printf("(%d, %d)\n",a.x,a.y);
}
void bfs()
{
queue<node>q;
node s,e;
s.x=0,s.y=0,vis[0][0]=1;
q.push(s);
while(q.size())
{
s=q.front();q.pop();
if(s.x==4&&s.y==4)print(s);
for(int i=0;i<4;++i)
{
e.x=s.x+dir[i][0];
e.y=s.y+dir[i][1];
if(!vis[e.x][e.y]&&e.x<5&&e.x>=0&&e.y<5&&e.y>=0&&map[e.x][e.y]==0)
{
pre[e.x][e.y]=s;
vis[e.x][e.y]=1;
q.push(e);
}
}
}
}
int main()
{
for(int i=0;i<5;++i)
for(int j=0;j<5;++j)
scanf("%d",&map[i][j]);
bfs();
return 0;
}
J - Oil Deposits HDU - 1241
DFS连通块消除标记,统计连通块的个数,没啥好讲的
#include <iostream>
#include <cstdio>
#include <cstring>
#define N 100
using namespace std;
char a[N][N];
int n,m;
int dx[8]={0,0,1,1,1,-1,-1,-1};
int dy[8]={1,-1,0,-1,1,0,1,-1};//根据题目自己定义八个方向,有时候是定义四个方向 根据题目意思决定,此种类型题目都需自己定义方向
void dfs(int x,int y)
{
int tempx,tempy;
for(int i=0;i<8;i++)
{
tempx=x+dx[i];
tempy=y+dy[i];
if(tempx>=0&&tempy>=0&&tempx<n&&tempy<m&&a[tempx][tempy]=='@')//确定满足条件时进行搜索,否则跳出
{
a[tempx][tempy]='*';
dfs(tempx,tempy);
}
else
continue;
}
}
int main()
{
int count;
while(cin>>n>>m,n!=0,m!=0)
{
count=0;
for(int i=0;i<n;i++)
cin>>a[i];
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(a[i][j]=='@')
{
count++;
dfs(i,j);
}
}
}
cout<<count<<endl;
}
return 0;
}
剩下的题目大同小异,没啥好说的,就这样了