Description
玩家鼠标先后点击两块棋子,试图将他们消去,然后游戏的后台判断这两个方格能不能消去。现在你的任务就是写这个后台程序。
Input
注意:询问之间无先后关系,都是针对当前状态的!
Output
Sample Input
3 4 1 2 3 4 0 0 0 0 4 3 2 1 4 1 1 3 4 1 1 2 4 1 1 3 3 2 1 2 4 3 4 0 1 4 3 0 2 4 1 0 0 0 0 2 1 1 2 4 1 3 2 3 0 0
Sample Output
YES NO NO NO NO YES
吐槽一下出题人文字表达能力,已在原文做修改。
代码:
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<vector>
#include<queue>
#include<stack>
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
#define MaxSize 1005
#define inf 0x3f3f3f3f
int n,m,flag;
int mp[MaxSize][MaxSize],book[MaxSize][MaxSize];
int d[4][2]= {0,1,1,0,0,-1,-1,0};//右,下,左,上
void dfs(int x, int y, int cnt, int dir,int tx, int ty)
{
if(x==tx&&y==ty)
{
flag=1;
return;
}
if(x<0 || x>=n || y<0 ||y>=m||book[x][y]==1||mp[x][y]!=0) return;
///开始剪枝///
if(cnt>2) return;
/*我最开始想的是,能走到这一步,说明前面说明没找到目标点,
如果此时的转弯次数大于等于2,再怎么走也不能在转弯2次内到目标点了。
才怪咧!这又不是step,如果当前已经转弯两次了,之后一直按这最后一次的方向走,走到目标点也是可以的呀*/
if(cnt==2)//如果已经转两次弯了,那么目标点必定只能在当前点方向的前方。
{
if(dir == 0)
{
if(!(x == tx && ty > y)) return;
}
else if(dir == 2)
{
if(!(x == tx && ty < y)) return;
}
else if(dir == 1)
{
if(!(y == ty && tx > x)) return;
}
else if(dir == 3)
{
if(!(y == ty && tx < x)) return;
}
}
///剪枝结束///
book[x][y]=1;
for(int i=0; i<4; i++)
{
if(dir==i)
dfs(x+d[i][0],y+d[i][1],cnt,dir,tx,ty);
else
dfs(x+d[i][0],y+d[i][1],cnt+1,i,tx,ty);
}
book[x][y]=0;
}
int main()
{
int x,y,tx,ty,q;
while(~scanf("%d%d",&n,&m))
{
if(n==0&&m==0) break;
for(int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
{
scanf("%d",&mp[i][j]);
}
}
scanf("%d",&q);
while(q--)
{
scanf("%d%d%d%d",&x,&y,&tx,&ty);
x--,y--,tx--,ty--;
if(mp[x][y]!=mp[tx][ty]||mp[x][y]==0||(x==tx&&y==ty)) printf("NO\n");
//注意如果是同一个点肯定是不能消去的【避坑之重复数据】
else
{
flag=0;
memset(book,0,sizeof(book));
book[x][y]=1;
for(int i=0; i<4; i++)
{
dfs(x+d[i][0],y+d[i][1],0,i,tx,ty);
if(flag) break;//这里也能剪枝,剪的还是大枝。我真机智。
}
if(flag) printf("YES\n");
else printf("NO\n");
}
}
}
return 0;
}//FROM CJZ
这道题其实不能从外面走还要简单一点,要是能从外面走的话,就在地图外面加一圈0即可。
思考:
1、在进入dfs判断的时候,我一开始是这样写的:
if(x<0 || x>=n || y<0 ||y>=m||book[x][y]==1||(mp[x][y]!=0&&(x!=tx&&y!=ty))) return;
if(x==tx&&y==ty)
{
flag=1;
return;
}
因为我想的是我有可能此时走到了目标点,而这个点是非0的,所以这里要留出个条件给这种情况。然而这样的话,出现下面这中情况我的代码就不对了:
5 5
1 2 1 2 1
0 0 0 2 0
0 0 0 0 0
0 0 0 0 0
1 1 1 1 1
1
1 4 1 2
因为有可能路径中存在和查询点一样的点,把路径挡住。所以没有到达最后目标点之前,我们都不能允许路径中出现非0点。所以应该改成这样:
if(x==tx&&y==ty)
{
flag=1;
return;
}
if(x<0 || x>=n || y<0 ||y>=m||book[x][y]==1||mp[x][y]!=0) return;
2、下面我们来说说这个结构:
void dfs(int x, int y, int cnt, int dir,int tx, int ty)
{
.
.
.
.
判断&剪枝
.
.
.
.
book[x][y]=1;
for(int i=0; i<4; i++)
{
if(dir==i)
dfs(x+d[i][0],y+d[i][1],cnt,dir,tx,ty);
else
dfs(x+d[i][0],y+d[i][1],cnt+1,i,tx,ty);
}
book[x][y]=0;
}
对于dfs而言,每一轮return之前,都出是走的一条路径。每一轮中,路径当中的所走过的每一个点都应该进行标记,来防止“贪吃蛇的自己咬到自己”的情况,即:走过的点不能重复走。
而对于这个结构而言,它先对当前点进行标记,然后对当前点进行扩展dfs。对于当前点,对由它为“起点”扩展出来的一堆堆点来说,这个当前点是被标记的,这很讲理嘛(自己想一想)。以这个点所有扩展出来的dfs都结束之后,再取消它的标记,结束以当前点为起始点的dfs路径。然后return到它的上一个点,上一个点又继续进行其他方向的dfs扩展。这种结构真是神奇,好好体会。
3、
void dfs(int x, int y, int cnt, int dir,int tx, int ty)
在bfs的时候,我们通常把点的其他属性(如:步数,时间)用结构体来存,这样在扩展的时候是没有什么问题的。而对于dfs,没有了bfs的队列节点储存结构,这些附加属性就可以写在函数的形参里面,这样扩展的时候这些属性就能照样进行步进了。
Description
For example ,consider the positive integer 145 = 1!+4!+5!, so it's a DFS number.
Now you should find out all the DFS numbers in the range of int( [1, 2147483647] ).
There is no input for this problem. Output all the DFS numbers in increasing order. The first 2 lines of the output are shown below.
Input
Output
Sample Output
1 2 ......
讲道理,这道题和dfs没一点关系。不过可以练习一下怎么打表。
打表代码:
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<vector>
#include<queue>
#include<stack>
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
#define MaxSize 45
#define inf 0x3f3f3f3f
int jc[100];
int main()
{
jc[0]=1;
int sum;
int cnt ;
int x ;
sum=1;
for(int i = 1; i <= 9; i++)
{
sum*=i;
jc[i]=sum;
}
FILE *fp=fopen("dfs_num.txt","w+");
for(int i=1; i<=2147483647; i++)
{
sum=0;
cnt = 0;
x = i;
while(x != 0)
{
sum+=jc[x%10];
x/=10;
}
if(sum==i)
{
fprintf(fp,"%d\\n",i);
//fflush(fp);
}
}
fclose(fp);
return 0;
}//FROM CJZ
最后出来4个数据1,2,145,40585,直接打表输出就可以了。
提交代码;
#include<stdio.h>
int main()
{
printf("1\n2\n145\n40585\n");
return 0;
}
Description
Input
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
Output
Sample Input
2 1 #. .# 4 4 ...# ..#. .#.. #... -1 -1
Sample Output
2 1
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<vector>
#include<queue>
#include<stack>
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
#define MaxSize 10
#define inf 0x3f3f3f3f
#define LL long long int
int n,k,ans;
int book[MaxSize];
char mp[MaxSize][MaxSize];
void dfs(int r, int cnt)
{
if(cnt == k)
{
ans++;
return;
}
if(r == n) return;
//上面这两个判断的顺序一定要这样写*
for(int i=0; i<n; i++)
{
if( mp[r][i]=='.' || book[i]==1) continue;
book[i]=1;//说明当前i可以选,则标记。然后cnt+1,继续找下一行
/**想一想如果是在最后一行(即n-1行)找到的最后一个cnt的情况,
这就决定了上面的return顺序。如果换一个顺序的话,这种情况下ans还没++就return了,
所以不行*/
dfs(r+1,cnt+1);
book[i]=0;
}
/*并不是每一行都能找到满足条件的棋盘位置的,
如果就这样结束了的话,这次for没找到位置,没进行往后的dfs,
那么后面就断了。所以不难想到,没找到的话,我们应该有个跳行语句。
而且我们知道,在选位置的时候不一定必须一行一行挨着选,
我们可以跳过某行或者某几行(只要是满足条件的)。如果我们就只写个for就完了的话,
这明显就不能实现跳行了,所以我们让每个点多一个可以选择跳行的语句。
从以上两点来看,无论哪种情况,都应该在for结束之后有跳行的语句,即对于每一个点,
它要进行两种选择,一是在下一行里面选择合适的位置,二是跳过下一行,进入再下一行去选。
这里举个例子,比如我们要隔两行再开始选,
那么这里我们会经历dfs(r+1,cnt)进入下一行,
下一行的时候又会经历dfs((r+1)+1,cnt),所以一定会囊括所有跳行情况*/
dfs(r+1,cnt);
}
int main()
{
while(~scanf("%d%d",&n,&k))
{
if(n==-1 && k==-1) break;
getchar();
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
{
scanf("%c",&mp[i][j]);
}
getchar();
}
memset(book,0,sizeof(book));
ans=0;
dfs(0,0);
printf("%d\n",ans);
}
return 0;
}//FROM CJZ