A-棋盘问题-POJ-1321
Sample Input
2 1
#.
.#
4 4
…#
…#.
.#…
#…
-1 -1
Sample Output
2
1
题意
- 简述概括为在n*n的棋盘上有标注“#”的位置可放棋子
- 输出在棋子均不同行不同列的情况下,有多少种方法摆放k个棋子
- 详见上述题面,已为中文版
题解
- 此题为搜索类题目,现选用DFS
- 递归出口应当是已按题意要求放完k个棋子,或遍历完棋盘仍无法成功放完k个棋子
- 对于不同行不同列的要求,此题解采用按行递归遍历各列
- 递归传入参数代表行数,用use数组标记被遍历的各列是否已被用过
- 在每一行中遍历n列,寻找在这一行有无合适的位置可以放置一颗棋子
- 若放置,则标记对应列的use数组,并累计放置的棋子个数,然后继续递归下一行
- 这一行一颗都不放也是可以的,循环结束后直接递归下一行
- 务必注意!x行选择在i列放置后,换成在其他列放置棋子,可能也满足题意
- 因此,递归结束后,一定要记得还原这一列未放置棋子的状态,并遍历下一列
- 即对应列的use数组和统计已放棋子个数的cnt要还原
- 最后递归结束时,如果是成功放置而return的,那么就在初始化为0的ans上自加,统计放置方法数
涉及知识点
- DFS和BFS 搜索算法(此题解用的是DFS)
- 对于DFS和BFS的搜索算法-详见链接博客介绍搜索入门
AC代码
#include<stdio.h>
#include<utility>
#include<string.h>
using namespace std;
const int maxn=10;
int n,k,cnt,ans;
char put[maxn][maxn];
int use[maxn];//标记某列是否已被用过
void dfs(int x)//递归行,每行最多用一次
{
if(cnt==k) {++ans;return;}//已用k列,代表已成功防止k个棋子
if(x>n) return;
for(int i=1;i<=n;++i)//枚举列,每列最多用一次
{
if(!use[i]&&put[x][i]!='.')//这一列还没用过,且不是空白区域
{
use[i]=1,++cnt;//在第x行用第i列
dfs(x+1);
use[i]=0,--cnt;//还原用第i列前的状态
}
//继续循环可选择在第x行用第i+1列等,第i列可能以后再用
}
dfs(x+1);//不用这行,直接下一行
}
int main()
{
while(~scanf("%d %d",&n,&k),n!=-1&&k!=-1)
{
memset(use,0,sizeof(use));
ans=0,cnt=0;
for(int i=1;i<=n;i++)
{
scanf("%s",put[i]+1);
}
dfs(1);
printf("%d\n",ans);
}
return 0;
}
B-Dungeon Master-POJ-2251
由于【引用】中难以输入一排连续的#,故下述输入输出的样例用代码块粘贴
*Sample Input*
3 4 5
S....
.###.
.##..
###.#
#####
#####
##.##
##...
#####
#####
#.###
####E
1 3 3
S##
#E#
###
0 0 0
*Sample Output*
Escaped in 11 minute(s).
Trapped!
题意
- 第一行输入L/R/C,分别代表空间高度、每层的行数、每层的列数
- 在这样一个空间中,要求从S出发,到达E
- 主角可向上下左右前后六个方向行走,每走一次耗费1min
- 求问从S走到E的最短时间或是否无法走到
- 分别输出Escaped in 最短耗时 minute(s). 或 Trapped!
题解
- 这是一个三维的搜索题,与二维相比只要把结构体元素、put和move均改为三维即可
- 此题解思路使用的是BFS,队列为空或已到终点时退出循环,分别返回**-1或最短耗时**
- 用三维的time数组记录走到每个点的最短耗时,那么若能走到终点,返回终点对应的time即可
- 关键思路在于!初始化为0的time还可代表某个点是否已被走到过,if中需要判断走过的点不能再走
- 因为需要输出的是最短耗时,早走到点A和晚走到点A对于之后的情况起点相同,但对于走点A肯定早走到的耗时少
- 所以就没有必要再记录绕路后的耗时
- 未越界+走得通+未走到过时间,应当更新对应点耗时+判断是否已到终点+新起点入队
- 最后判断返回值,若为 -1 则输出 Trapped!,否则输出返回的最短耗时
涉及知识点
- DFS和BFS 搜索算法(此题解用的是BFS)
- 对于DFS和BFS的搜索算法-详见链接博客介绍搜索入门
AC代码
#include<stdio.h>
#include<string.h>
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=50;
char put[maxn][maxn][maxn];
int L,R,C,sum,ans,time[maxn][maxn][maxn];
int move[6][3]={{-1,0,0},{1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1}};
struct node{int x,y,z;}start,ed,cur,next;//起点,终点,当前,走向的目标位置
int bfs()
{
queue<node> Q;//为了节省循环pop清空队列Q的时间,索性多组输入时每次重建Q即可
Q.push(start);
while(Q.size())
{
cur=Q.front(),Q.pop();
for(int i=0;i<6;i++)//遍历左右前后上下六个方位
{
next.x=cur.x+move[i][0];
next.y=cur.y+move[i][1];
next.z=cur.z+move[i][2];
if(next.x>=0&&next.x<R&&next.y>=0&&next.y<C&&next.z>=0&&next.z<L&&put[next.z][next.x][next.y]!='#'&&time[next.z][next.x][next.y]==0)
{//行、列、高均未越界 且 往当前遍历到的方向走得通 且 还未走到过这个目标位置(不然来回走就不是最短时间了)
time[next.z][next.x][next.y]=time[cur.z][cur.x][cur.y]+1;//更新走到目标位置耗费的时间
if(next.x==ed.x&&next.y==ed.y&&next.z==ed.z) return time[ed.z][ed.x][ed.y];//走到了终点
Q.push(next);//目标位置入队,接下来从目标位置开始走
}
}
}
return -1;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
while(cin>>L>>R>>C&&L+R+C)
{
sum=0;
memset(time,0,sizeof(time));
for(int i=0;i<L;++i)
{
for(int j=0;j<R;++j)
{
for(int k=0;k<C;++k)
{
cin>>put[i][j][k];
if(put[i][j][k]=='S') start.z=i,start.x=j,start.y=k;
else if(put[i][j][k]=='E') ed.z=i,ed.x=j,ed.y=k;
}
}
}
ans=bfs();
if(ans==-1) cout<<"Trapped!"<<endl;
else cout<<"Escaped in "<<ans<<" minute(s)."<<endl;
}
return 0;
}
C-Catch That Cow-POJ-3278
Sample Input
5 17
Sample Output
4
题意
- 第一行输入N/K,代表农夫初始位置和牛所在的位置,他们都在一条直线的路径上
- 农夫每次可以后退一步、前进一步或者直接移动到坐标两倍处,三种移动方式每次耗时均是1min
- 输出农夫从N移动到K的最短耗时
题解
- 这是一个一维的搜索题,移动方式有三种
- ok函数只需判断是否越界,耗时最短可由sign标记是否已到过来保证
- while循环中分别判断三种移动方式,且队列先进先出
- 因此,每一次三种情况if合理的入队后,会在各自下一步之前先被取出来
- 最后,当Move取出的当前位置坐标已到K时,return退出函数,ans保存了当前位置对应的step即最短耗时,输出即可
涉及知识点
- DFS和BFS 搜索算法(此题解用的是BFS)
- 对于DFS和BFS的搜索算法-详见链接博客介绍搜索入门
AC代码
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<queue>
using namespace std;
const int maxn=1e5+10;
int pos,N,K,ans,sign[maxn];
struct movement
{
int pos;
int step;
}Move,t;
bool ok(int x)
{
if(x<0||x>maxn) return false;
return true;
}
void bfs()
{
queue<movement> q;
memset(sign,0,sizeof(sign));
Move.pos=N;//从N开始走
Move.step=0;//初始化时间为0
q.push(Move);
sign[N]=1;//标记是否走到过这个位置,重复走就不是最快的了
while(!q.empty())
{
Move=q.front();
/*
队列是先进先出的,所以每一次三种情况if合理的入队后
会在各自下一步之前先被取出来
*/
q.pop();
if(Move.pos==K)
{
ans=Move.step;
return;
}
else t.step=Move.step+1;//用t记录走到下一个位置的耗时和位置
t.pos=Move.pos+1;
if(ok(t.pos)&&sign[t.pos]==0)
{
sign[t.pos]=1;
q.push(t);
//t入队后再用front提取move,得到的步数和位置都是更新过了的
}
t.pos=Move.pos-1;
if(ok(t.pos)&&sign[t.pos]==0)
{
sign[t.pos]=1;
q.push(t);
}
t.pos=Move.pos*2;
if(ok(t.pos)&&sign[t.pos]==0)
{
sign[t.pos]=1;
q.push(t);
}
}
}
int main()
{
scanf("%d %d",&N,&K);
ans=0x3F3F3F3F;
bfs();
printf("%d\n",ans);
return 0;
}
D-Fliptile-POJ-3279
Sample Input
4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1
Sample Output
0 0 0 0
1 0 0 1
1 0 0 1
0 0 0 0
题意
- 有一个m行n列的01矩阵(下附代码因个人习惯,看做n行m列)
- 每次操作选择一个格子,使得该格子与上下左右四个格子的值翻转(翻转即0变为1或1变为0)
- 输出至少多少次操作可以使得矩阵中所有的值变为0
- 如果不可能成功操作,则输出IMPOSSIBLE
题解
- 这道题借鉴搜索思维的不是很多,主要就是翻转上下左右和自己的五个move操作
- 这类题目属于 翻转类开关问题,首先需要明白,翻转的顺序并不影响最终结果
- 其次要知道翻转2*k次=没翻转,因此可以用对2取余的操作来简化翻转次数,且 最后输出的矩阵只有0或1
- 第一行是否翻转用m位的二进制数来表示(十进制里面就是 [0,2m)),第i位代表第i列,1翻转0不翻转
- 同时,对第一行 [0,2m) 的遍历方式也 遵循了字典序
- 第二行开始,根据截至当前位置,被遍历位置的上方是否需要翻转,来决定这个位置是否需要翻转
- 所谓截至当前位置,那么就要把前面所有翻转操作对上方位置的影响进行计算
- 初始化表示翻转方案的数组全部为0,那么没遍历到的肯定算作没翻转
- 用一个函数来计算某位置被上下左右和自己是否翻转后所得的数,然后在决定下面位置是否翻转的函数中调用即可
- 因为每次都要保证前一行都被翻转至0,所以最后一行翻转完毕后,只要 判断最后一行是否全是0,就能知晓是否有可行方案
- 决定是否翻转的函数中顺便统计翻转次数并输出
- 最后比较得到最小的翻转次数,每次比较出更小的次数时,就把当前翻转方案复制到最优解数组中,可用memcpy整体复制
- 这些操作主要需要用到三个数组:存图+当前翻转方案+最优解方案
- 具体详见代码行批注
涉及知识点
- DFS和BFS 搜索算法(此题解用的是BFS)
- 位运算 思维的处理方式(寒假集训笔记中未涉及,详细内容后续更新)
- 对于DFS和BFS的搜索算法-详见链接博客介绍搜索入门
AC代码
#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
int a[20][20],b[20][20],c[20][20];//a存图;b保存当前翻转方案;c保存最优解
int move[5][2]={{-1,0},{1,0},{0,0},{0,-1},{0,1}};//五个翻转走向
int n,m,ans=inf;//翻转类开关问题:翻转的顺序并不影响最终结果
bool getcolor(int x,int y)//翻动x,y周围的瓷砖,得到x,y的颜色
{
int res=a[x][y];
for (int i=0;i<5;++i)
{
int fx=x+move[i][0],fy=y+move[i][1];
if(fx>=1&&fx<=n&&fy>=1&&fy<=m) res+=b[fx][fy];//b[fx][fy]是1就要翻它
}//实际只考虑这个位置之前之上和自己翻转的影响,其他还未遍历到改变,且初始化为0
return res%2;//原0则翻奇次变1;原1则翻奇次变0
}
int solve()
{
int res=0;//从第二行开始检查是否需要翻转,上一行是1就要靠其下对应的这一行位置来翻
for(int i=2;i<=n;++i) {for(int j=1;j<=m;++j) {if(getcolor(i-1,j)) b[i][j]=1;}}
for(int i=1;i<=m;++i) {if(getcolor(n,i)) return inf;}//检查最后一行是否全为0
for(int i=1;i<=n;++i) {for(int j=1;j<=m;j++) res+=b[i][j];}//统计翻转次数
return res;
}
int main()
{
scanf("%d %d",&n,&m);//n行m列
for(int i=1;i<=n;++i) {for(int j=1;j<=m;++j) scanf("%d",&a[i][j]);}
for(int s=0;s<1<<m;++s)//按照字典序枚举第一行所有翻转可能,1翻0不翻
{
memset(b,0,sizeof(b));//初始化都不翻转
for(int i=1;i<=m;++i) b[1][i]=(s>>(m-i))&1;//取出s在二进制表示下的第m-i位,二进制第几位是从右数的
int t=solve();//当前情况翻转次数
if(t<ans) {ans=t;memcpy(c,b,sizeof(b));}//此处的memcpy复制b中sizeof(b)个字节到c
}
if(ans==inf) printf("IMPOSSIBLE\n");
else
{
for(int i=1;i<=n;++i) {for(int j=1;j<=m;++j) printf("%d%c",c[i][j],j==m?'\n':' ');}
}
return 0;
}