寒假“搜索”练习题解

1、P1605 迷宫

题目背景

给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过。给定起点坐标和终点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案。在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。

题目描述

输入格式

第一行N、M和T,N为行,M为列,T为障碍总数。第二行起点坐标SX,SY,终点坐标FX,FY。接下来T行,每行为障碍点的坐标。

输出格式

给定起点坐标和终点坐标,问每个方格最多经过1次,从起点坐标到终点坐标的方案总数。

输入输出样例

输入 #1复制

2 2 1
1 1 2 2
1 2

输出 #1复制

1

说明/提示

【数据规模】

1≤N,M≤5

题解:使用深度搜索,用a数组存迷宫地形,book数组存该点是否走过,在main函数里面先将起始地址的状态改为1(走过),然后开始调用深搜函数。在函数里面定义一个二维数组,分别存顺序为上下左右移动的下标,然后通过循环遍历四个方向,先判断,移动过后的px,py是否越界,越界则跳过,再判断在px,py处的地形是否可走且未走过,满足情况则将该点改为走过状态并进行下一点,函数调用完后再将状态还原(回溯)。函数满足x,y都到达给定终点则cnt++(记数)。

#include <stdio.h>
int n,m,t,sx,sy,fx,fy,a[7][7],book[7][7],cnt;
void dfs(int x,int y)
{ if(x==fx&&y==fy)
  { cnt++;
    return;
  }
  int zb[4][4]={{0,1},{1,0},{0,-1},{-1,0}},px,py;
  for(int i=0;i<4;i++)
  { px=x+zb[i][0];
    py=y+zb[i][1];
    if(px<1||px>n||py<1||py>m)
     continue;
    if(!book[px][py]&&!a[px][py]) 
    {book[px][py]=1; 
	 dfs(px,py);
	 book[px][py]=0;
	}
  }
  return;
}
main()
{ scanf("%d%d%d",&n,&m,&t);
  scanf("%d%d%d%d",&sx,&sy,&fx,&fy);
  while(t--)
  { int zx,zy;
    scanf("%d%d",&zx,&zy);
    a[zx][zy]=1;
  }
  book[sx][sy]=1;
  dfs(sx,sy);
  printf("%d",cnt);
 } 

 2、P2404 自然数的拆分问题

题目描述

任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。现在给你一个自然数n,要求你求出n的拆分成一些数字的和。每个拆分后的序列中的数字从小到大排序。然后你需要输出这些序列,其中字典序小的序列需要优先输出。

输入格式

输入:待拆分的自然数n。

输出格式

输出:若干数的加法式子。

输入输出样例

输入 #1复制

7

输出 #1复制

1+1+1+1+1+1+1
1+1+1+1+1+2
1+1+1+1+3
1+1+1+2+2
1+1+1+4
1+1+2+3
1+1+5
1+2+2+2
1+2+4
1+3+3
1+6
2+2+3
2+5
3+4

说明/提示

用回溯做。。。。

n≤8

 题解:在主函数中调用dfs传入step下标初始值0和sum总和初始值0,在dfs中当已存数之和sum==n即为找到一种情况按格式输出即可,整体递归回溯结构为,用循环遍历1——n-1,第一个累加元素只需判断是否符合<=n,之后元素则还需在此基础上加上条件>=a[step-1](即当前元素需要大于等于前面已存元素保证整个序列的升序以及整体的不重复,因为1,1,5和5,1,1是一种)。

#include <stdio.h>
int n,a[10];
void dfs(int step,int sum)//step存元素数组的下标,数组中元素和 
{if(sum==n)
 {for(int i=0;i<step-1;i++)
   printf("%d+",a[i]);
  printf("%d\n",a[step-1]);
  return;  
 }
 for(int i=1;i<n;i++)
 { if(sum+i<=n&&step==0)//第一个元素只需考虑存的数是否大于n 
   {sum+=i;
    a[step]=i; 
    dfs(step+1,sum);
    sum-=i; //还原状态,找其他组合(回溯)
	}
   else if(sum+i<=n&&step>0&&i>=a[step-1])//之后的元素都要判断是否当前存进去的数,大于或等于已存入数(保证升序且不会重复) 
   {sum+=i;
    a[step]=i; 
    dfs(step+1,sum);
    sum-=i;
	} 	
 }
}
main()
{scanf("%d",&n);
 dfs(0,0);
 return;
}

 3、P2036 PERKET

题目描述

Perket 是一种流行的美食。为了做好 Perket,厨师必须谨慎选择食材,以在保持传统风味的同时尽可能获得最全面的味道。你有 nn 种可支配的配料。对于每一种配料,我们知道它们各自的酸度 ss 和苦度 bb。当我们添加配料时,总的酸度为每一种配料的酸度总乘积;总的苦度为每一种配料的苦度的总和。

众所周知,美食应该做到口感适中,所以我们希望选取配料,以使得酸度和苦度的绝对差最小。

另外,我们必须添加至少一种配料,因为没有任何食物以水为配料的。

输入格式

第一行一个整数 n,表示可供选用的食材种类数。

接下来 n 行,每行 2 个整数 si​ 和 bi​,表示第 i 种食材的酸度和苦度。

输出格式

一行一个整数,表示可能的总酸度和总苦度的最小绝对差。

输入输出样例

输入 #1复制

1
3 10

输出 #1复制

7

输入 #2复制

2
3 8
5 8

输出 #2复制

1

输入 #3复制

4
1 7
2 6
3 8
4 9

输出 #3复制

1

说明/提示

数据规模与约定

对于100% 的数据,有1≤n≤10,且将所有可用食材全部使用产生的总酸度和总苦度小于1×10^9,酸度和苦度不同时为 1 和 0。

题解: 本题可以理解为求集合的非空子集,如你有三种食材a、b、c,你可以有{a},{b},{c},{a,b},{a,c},{b,c},{a,b,c}几种选法,并找出这几种选择中哪种选法的酸度乘积-苦度累加的绝对值最小,即满足题意。那么本题就可以看做为找n个数能组合的子集个数,并求出苦度,酸度。而你可以简单用二进制表示为子集选法如n=3,即有3个二进制位数,001->{c},010->{b},100->{a},011->{b,c},101->{a,c},110->{a,b},111->{a,b,c},而001,010,100……就是1,2,3……(1<<n)-1 (1<<n这个很关键)。

//本题可类比为用二进制表示n个数的子集,然后再通过比较所有子集 
#include <stdio.h>
#include <math.h>
main()
{ int n;
  int s[15],b[15],min=999999999;
  scanf("%d",&n);
  for(int i=1;i<=n;i++)
   scanf("%d%d",&s[i],&b[i]);
  for(int i=1;i<(1<<n);i++) //1<<n n位二进制可以组合成2^n个01组合 
  {int k=i,j,ss,bb,wz=1,book[15],cnt=1;//wz记录n位的二进制中1的位置 
   while(k)
   { if(k&1)//判断n位二进制中1的位置 
      book[cnt++]=wz;
     k/=2;//即k右移一位 
	 wz++;
	} 
	for(j=1,ss=1,bb=0;j<cnt;j++)
	 { ss*=s[book[j]];
	   bb+=b[book[j]];
	 }
	 if(abs(ss-bb)<min) min=abs(ss-bb);
  } 
  printf("%d",min); 
}

 4、P1596 Lake Counting S

题目描述

Due to recent rains, water has pooled in various places in Farmer John's field, which is represented by a rectangle of N x M (1 <= N <= 100; 1 <= M <= 100) squares. Each square contains either water ('W') or dry land ('.'). Farmer John would like to figure out how many ponds have formed in his field. A pond is a connected set of squares with water in them, where a square is considered adjacent to all eight of its neighbors. Given a diagram of Farmer John's field, determine how many ponds he has.

由于近期的降雨,雨水汇集在农民约翰的田地不同的地方。我们用一个NxM(1<=N<=100;1<=M<=100)网格图表示。每个网格中有水('W') 或是旱地('.')。一个网格与其周围的八个网格相连,而一组相连的网格视为一个水坑。约翰想弄清楚他的田地已经形成了多少水坑。给出约翰田地的示意图,确定当中有多少水坑。

输入格式

Line 1: Two space-separated integers: N and M * Lines 2..N+1: M characters per line representing one row of Farmer John's field. Each character is either 'W' or '.'. The characters do not have spaces between them.

第1行:两个空格隔开的整数:N 和 M 第2行到第N+1行:每行M个字符,每个字符是'W'或'.',它们表示网格图中的一排。字符之间没有空格。

输出格式

Line 1: The number of ponds in Farmer John's field.

一行:水坑的数量

输入输出样例

输入 #1复制

10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.

输出 #1复制

3

说明/提示

OUTPUT DETAILS: There are three ponds: one in the upper left, one in the lower left, and one along the right side.

题解: 在主函数中遍历找出水(‘w’),再调用函数可用深度搜索找出所有与之相连的水坑,它们一起组成了一个大水坑,此时大水坑个数加一,然后将他们都变为旱地,或者用另外一个标记函数,将所有相连的小水坑标记为已找过,防止重复计算同一水坑。这样就能找出所有水坑。

#include <stdio.h>
char a[110][110];
int n,m,cnt;
void dfs(int x,int y) 
{ int zb[8][2]={{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1}};//水坑延伸的八个方向 
  int px,py;
  for(int i=0;i<8;i++)
  {px=x+zb[i][0];
   py=y+zb[i][1];
   if(px<0||py<0||px>n-1||py>m-1)
    continue;
   if(a[px][py]=='W') //找到与传过来的水坑相连的水坑即将它变成旱地,无需将它还原,因为无需回溯 
   {a[px][py]='.';
    dfs(px,py);
	}	 
  }
} 
main()
{scanf("%d%d",&n,&m);
 for(int i=0;i<n;i++)
  scanf("%s",a[i]);
 for(int i=0;i<n;i++)
  for(int j=0;j<m;j++)
   if(a[i][j]=='W')
    {cnt++;  //水坑个数加一 
	 a[i][j]='.'; //将该点变为旱地以便后面不会重复计算该大水坑 
	 dfs(i,j); //再用深搜把与该水坑相连的全部水坑都找出来,变成旱地 
	}  
 printf("%d\n",cnt);
}

5、P1101 单词方阵

题目描述

给一n×n的字母方阵,内可能蕴含多个“yizhong”单词。单词在方阵中是沿着同一方向连续摆放的。摆放可沿着 8 个方向的任一方向,同一单词摆放时不再改变方向,单词与单词之间可以交叉,因此有可能共用字母。输出时,将不是单词的字母用*代替,以突出显示单词。例如:

输入:
    8                     输出:
    qyizhong              *yizhong
    gydthkjy              gy******
    nwidghji              n*i*****
    orbzsfgz              o**z****
    hhgrhwth              h***h***
    zzzzzozo              z****o**
    iwdfrgng              i*****n*
    yyyygggg              y******g

输入格式

第一行输入一个数n。(7≤n≤100)。

第二行开始输入n×n的字母矩阵。

输出格式

突出显示单词的n×n矩阵。

输入输出样例

输入 #1复制

8
qyizhong
gydthkjy
nwidghji
orbzsfgz
hhgrhwth
zzzzzozo
iwdfrgng
yyyygggg

输出 #2复制

*yizhong
gy******
n*i*****
o**z****
h***h***
z****o**
i*****n*
y******g

 题解:
        本题具有连通块的思想,整体思想是用一个二维整型数组把符合条件的所有单词都标记出来,在输出时把满足状态的字符直接输出,不满足状态的则用‘*’表示。
        可以在主函数中先找到单词的首字母,作为进入深搜的入口,并先把它的下标先暂存起来,以便整体满足单词情况时能知道所有字母下标,在传入dfs参数时,给出单词延伸的方向这个是很重要的,确保不会出现字母随意上下左右连接成单词,我在第一次做的时候就因为没给定方向被卡住了很久,而要给定方向也很简单,可以用一个循环遍历八个数传进去,代表八个方向。在dfs里就只要确保朝同一个方向(八个方向)能组成单词即可,具体操作就是找在同一个方向的字母与给定单词的顺序一一对应的单词。

#include <stdio.h>
#include <string.h>
int n,book[110][110],v[7][2];
char a[110][110],b[10]="yizhong";
void dfs(int x,int y,int step,int dir)//x,y坐标,step数组下标,dir单词延伸方向 
{ if(step>=7)
  { for(int i=0;i<7;i++)
    book[v[i][0]][v[i][1]]=1;//能构成单词,则用book改变它们各个单词的状态,以便最后输出 
	return;
  }
  int zb[8][2]={{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1}},px,py;
  for(int i=0;i<8;i++)
  {px=x+zb[i][0];
   py=y+zb[i][1];
   if(px<0||px>n-1||py<0||py>n-1)
     continue;
   if(a[px][py]==b[step]&&dir==i) //a[px][py]==b[step]保证延伸方向的下一个字符与"yizhong"对应的字符相等 
    {v[step][0]=px;               //dir==i 保证往同一方向延伸,不会上下左右摇摆组成一串"yizhong"
     v[step][1]=py;
     dfs(px,py,step+1,i);
	 } 
  }
}
main()
{ scanf("%d",&n);
  for(int i=0;i<n;i++)
   scanf("%s",a[i]);
  for(int i=0;i<n;i++)
   for(int j=0;j<n;j++)
    if(a[i][j]=='y')
    {v[0][0]=i; //将"yizhong"中第一个Y的下标存起来 
     v[0][1]=j;
     for(int k=0;k<8;k++) //y向八个方向延伸 
	 dfs(i,j,1,k);
	}
  for(int i=0;i<n;i++)
   {for(int j=0;j<n;j++)
     if(book[i][j]==1) // 是"yizhong"则输出,否则输出'*' 
	  printf("%c",a[i][j]);
	 else
	  printf("*");
	printf("\n");  
   }
}

 6、P1162 填涂颜色

题目描述

由数字0组成的方阵中,有一任意形状闭合圈,闭合圈由数字1构成,围圈时只走上下左右4个方向。现要求把闭合圈内的所有空间都填写成2.例如:6×6的方阵(n=6),涂色前和涂色后的方阵如下:

0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 2 2 1
1 1 1 1 1 1

输入格式

每组测试数据第一行一个整数n(1≤n≤30)

接下来n行,由0和1组成的n×n的方阵。

方阵内只有一个闭合圈,圈内至少有一个0。

//感谢黄小U饮品指出本题数据和数据格式不一样. 已修改(输入格式)

输出格式

已经填好数字22的完整方阵。

输入输出样例

输入 #1复制

6
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1

输出 #1复制

0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 2 2 1
1 1 1 1 1 1

说明/提示

1≤n≤30

 题解:要找闭合圈内的0并不容易,因为整个矩阵只有0和1两个值,所以咱们可以将所有圈外的0都标记出来,到时候输出碰到被标记的圈外0直接输出,遇到未被标记的0输出2即可。
首先可以将数组空间开大点,让整个矩阵外面再包一圈0,可以将所有的圈外0连接成一个整体,就不需要担心圈外有多少处0了,然后就直接从周围的任意一点进入dfs(因为被一圈0包围了,所以肯定是圈外0),将连接起来的圈外0都标记起来。在dfs内如果越界了则return,否则都可以标记起来,这样就实现了标记出所有圈外0。

#include <stdio.h>
int n,a[35][35],book[35][35];//a数组开大点,让所有不是闭合圆中的0相连 
void dfs(int x,int y)
{int zb[4][2]={{0,1},{1,0},{0,-1},{-1,0}},px,py;
 if(x<0||x>n+1||y<0||y>n+1) //越界则返回 
  return;
 book[x][y]=1; //没有越界则将所有非闭合圆中的0都标记 
 for(int i=0;i<4;i++)
 {px=x+zb[i][0];
  py=y+zb[i][1];
  if(a[px][py]==0&&!book[px][py])//是圈外0且未被标记过 
  dfs(px,py); 
  }
}
main()
{scanf("%d",&n);
 for(int i=1;i<=n;i++)
  for(int j=1;j<=n;j++)
   scanf("%d",&a[i][j]);
 dfs(0,0);//随便从圈外的地址进入dfs 
 for(int i=1;i<=n;i++)
 {for(int j=1;j<=n;j++)
   if(a[i][j]==0&&!book[i][j])
    printf("2 ");
   else
    printf("%d ",a[i][j]); 
   printf("\n");
  } 
}

7、奇怪的电梯

题目描述

呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第i层楼(1≤i≤N)上有一个数字Ki​(0≤Ki​≤N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如:3, 3 ,1 ,2 ,5代表了Ki​(K1​=3,K2​=3,…),从1楼开始。在1楼,按“上”可以到4楼,按“下”是不起作用的,因为没有-2楼。那么,从A楼到B楼至少要按几次按钮呢?

输入格式

共二行。

第一行为3个用空格隔开的正整数,表示N,A,B(1≤N≤200, 1≤A,B≤N)。

第二行为N个用空格隔开的非负整数,表示Ki。

输出格式

一行,即最少按键次数,若无法到达,则输出-1。

输入输出样例

输入 #1复制

5 1 5
3 3 1 2 5

输出 #1复制

3

题解: 本题比较简单,可以用深搜,也可以用广搜,广搜我不太熟练,所以还是用了深搜。这题跟迷宫差不多都是一个类型,大致代码也一样,只是迷宫有四个方向,而电梯只有上下两个方向,你也可以用一个循环,循环1跟-1,1则是代表向上,-1则表示向下。用1或-1乘以当前楼层数组再加上当前楼层就是下一楼层了,只需再判断一下是否越界即可 ,当然也还需标记一下走过楼层,到时候再回溯。不过值得注意的是这题还需要判断一下当前步数是否大于最小步数,如果大于就没必要再走了,会浪费很多时间,我也是在这个地方T了很久。

#include <stdio.h>
int n,a,b,k[210],book[210],min=999999999;
void dfs(int c,int step)
{if(step>min) //没有这句会超时两个 
  return;
 if(c==b)
 { if(step<min) min=step;
   return;
 }
 int next[2]={1,-1},pc;//1向上,-1向下 
 for(int i=0;i<2;i++)
 {pc=c+next[i]*k[c];
  if(pc<1||pc>n)
   continue;
  if(!book[pc]) 
  {book[pc]=1; 
   dfs(pc,step+1);
   book[pc]=0;
  }  
 }
}
main()
{ scanf("%d%d%d",&n,&a,&b);
  for(int i=1;i<=n;i++)
   scanf("%d",&k[i]);
  book[a]=1; 
  dfs(a,0);
  if(min==999999999)
   printf("-1");
  else 
   printf("%d",min);
} 

8、马的遍历 

题目描述

有一个 n×m 的棋盘,在某个点 (x,y) 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。

输入格式

输入只有一行四个整数,分别为 n, m, x, y。

输出格式

一个n×m 的矩阵,代表马到达某个点最少要走几步(左对齐,宽 5 格,不能到达则输出 -1)。

输入输出样例

输入 #1复制

3 3 1 1

输出 #1复制

0    3    2    
3    -1   1    
2    1    4    

说明/提示

数据规模与约定

对于全部的测试点,保证1≤x≤n≤400,1≤y≤m≤400。

题解:本题可以用bfs,创一个队列,队列元素为坐标和步数,用bfs特性将所有能跳到的点的步数都记录下来,最终不能跳到的点就还是0,输出的时候遇到0只要不是初始点坐标就直接输出-1。
实现这个操作的具体过程就是,从初始点开始蔓延,并将初始点赋为0,遇到能跳到的点就将它们入队,将它们标记为已走过并记录下到该点所需的步数(步数为队首步数加1,因为所有元素都是队首蔓延出去的下一个),当队首能蔓延到的所有元素都入队时,将队首出列,接着重复刚才操作执行下一个队首,直到所有能跳到的点都被标记并入队出队。没被标记过得自然就是不能到达的点。

#include <stdio.h>
int n,m,x,y,a[410][410],book[410][410];
struct que
{ int x;
  int y;
  int step;
}p[160010];//最多只有160000个点 
main()
{ int head=1,tail=1;
  scanf("%d%d%d%d",&n,&m,&x,&y);
  a[x][y]=0;//它本身的步数肯定是0 
  book[x][y]=1;
  p[tail].x=x;
  p[tail++].y=y;//入列 
  while(head<tail)
  {int zb[8][2]={{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,-1},{-2,1}},px,py;//马能跳的八个方向 
   for(int i=0;i<8;i++)
   {px=p[head].x+zb[i][0];//记录队首元素能延伸到的点 
    py=p[head].y+zb[i][1];
    if(px<1||px>n||py<1||py>m)//越界 
     continue;
    if(!book[px][py])//未走过 
     {book[px][py]=1;
      p[tail].x=px;//入列 
	  p[tail].y=py;
	  p[tail++].step=p[head].step+1;//步数为队首元素加1,以便之后找步数 
	  a[px][py]=p[head].step+1;//同样将步数存到二维数组里 
	 }
	}
	head++; 	 
  }
  for(int i=1;i<=n;i++)
  {for(int j=1;j<=m;j++)
    if(a[i][j]==0&&(i!=x||j!=y))//是0但又不是起始点,那么一定就是跳不到的点 
     printf("%-5d",-1);
    else 
	 printf("%-5d",a[i][j]);
   printf("\n"); 
  }
} 

 9、P1219 [USACO1.5]八皇后 Checker Challenge

题目描述

一个如下的 6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。

上面的布局可以用序列 2 4 6 1 3 5 来描述,第 i 个数字表示在第 i 行的相应位置有一个棋子,如下:

行号 1 2 3 4 5 6

列号 2 4 6 1 3 5

这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 3 个解。最后一行是解的总个数。

输入格式

一行一个正整数 n,表示棋盘是 n×n 大小的。

输出格式

前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。

输入输出样例

输入 #1复制

6

输出 #1复制

2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4

说明/提示

【数据范围】
对于 100% 的数据,6≤n≤13。

题目翻译来自NOCOW。

USACO Training Section 1.5

 题解:本题按正常情况做的话可能会超时,所以咱们只能用另外一种方法做。需要用到四个数组,分别用来:存每行皇后放的列位置,该列是否存在皇后(即会被攻击),斜下线是否存在皇后,斜上线是否存在皇后。然后从第一行开始找每行合适放皇后的位置,如果该点的列,斜上线,斜下线都没有皇后则可以放,然后再递归回溯,直到找到每行放皇后的位置,如果能成功放置所有皇后(即行数到了n行)那么就开始输出,要注意的是本题只需输出前三组皇后放置的位置。

#include <stdio.h>
int n,cnt,a[100],b[100],c[100],d[100];//a 行,b列,c 斜下线,d 斜上线  
void dfs(int step)
{ if(step>n)
  {cnt++;
   if(cnt<=3)
   {for(int i=1;i<=n;i++)
     printf("%d ",a[i]);
    printf("\n");  
	}
	return;
  }
  for(int i=1;i<=n;i++)//遍历列数 
  {if(!b[i]&&!c[step+i]&&!d[step-i+n]) //列 ,斜上线和斜下线都没有元素则可放
   {a[step]=i;
    b[i]=1;c[step+i]=1;d[step-i+n]=1;//该点放进皇后,则将它的列 ,斜上线和斜下线都标记为被攻击状态 
	dfs(step+1);
    b[i]=0;c[step+i]=0;d[step-i+n]=0;//还原,以便回溯 
   }  
  }
}
main()
{scanf("%d",&n);
 dfs(1);
 printf("%d\n",cnt);
} 

10、P1825 [USACO11OPEN]Corn Maze S 

题目描述

This past fall, Farmer John took the cows to visit a corn maze. But this wasn't just any corn maze: it featured several gravity-powered teleporter slides, which cause cows to teleport instantly from one point in the maze to another. The slides work in both directions: a cow can slide from the slide's start to the end instantly, or from the end to the start. If a cow steps on a space that hosts either end of a slide, she must use the slide.

The outside of the corn maze is entirely corn except for a single exit.

The maze can be represented by an N x M (2 <= N <= 300; 2 <= M <= 300) grid. Each grid element contains one of these items:

* Corn (corn grid elements are impassable)

* Grass (easy to pass through!)

* A slide endpoint (which will transport a cow to the other endpoint)

* The exit

A cow can only move from one space to the next if they are adjacent and neither contains corn. Each grassy space has four potential neighbors to which a cow can travel. It takes 1 unit of time to move from a grassy space to an adjacent space; it takes 0 units of time to move from one slide endpoint to the other.

Corn-filled spaces are denoted with an octothorpe (#). Grassy spaces are denoted with a period (.). Pairs of slide endpoints are denoted with the same uppercase letter (A-Z), and no two different slides have endpoints denoted with the same letter. The exit is denoted with the equals sign (=).

Bessie got lost. She knows where she is on the grid, and marked her current grassy space with the 'at' symbol (@). What is the minimum time she needs to move to the exit space?

去年秋天,奶牛们去参观了一个玉米迷宫,迷宫里有一些传送装置,可以将奶牛从一点到另一点进行瞬间转移。这些装置可以双向使用:一头奶牛可以从这个装置的起点立即到此装置的终点,同时也可以从终点出发,到达这个装置的起点。如果一头奶牛处在这个装置的起点或者终点,这头奶牛就必须使用这个装置。

玉米迷宫的外部完全被玉米田包围,除了唯一的一个出口。

这个迷宫可以表示为N×M的矩阵(2 ≤ N ≤ 300; 2 ≤ M ≤ 300),矩阵中的每个元素都由以下项目中的一项组成:

 玉米,这些格子是不可以通过的。

 草地,可以简单的通过。

 一个装置的结点,可以将一头奶牛传送到相对应的另一个结点。

 出口

奶牛仅可以在相邻两个格子之间移动,要在这两个格子不是由玉米组成的前提下才可以移动。奶牛能在一格草地上可能存在的四个相邻的格子移动。从草地移动到相邻的一个格子需要花费一个单位的时间,从装置的一个结点到另一个结点需要花费0个单位时间。

被填充为玉米的格子用“#”表示,草地用“.”表示,每一对装置的结点由相同的大写字母组成“A-Z”,且没有两个不同装置的结点用同一个字母表示,出口用“=”表示。

Bessie在这个迷宫中迷路了,她知道她在矩阵中的位置,将Bessie所在的那一块草地用“@”表示。求出Bessie需要移动到出口处的最短时间。

例如以下矩阵,N=5,M=6:

###=##
#.W.##
#.####
#.@W##
######

唯一的一个装置的结点用大写字母W表示。

最优方案为:先向右走到装置的结点,花费一个单位时间,再到装置的另一个结点上,花费0个单位时间,然后再向右走一个,再向上走一个,到达出口处,总共花费了3个单位时间。

输入格式

第一行:两个用空格隔开的整数N和M;

第2-N+1行:第i+1行描述了迷宫中的第i行的情况(共有M个字符,每个字符中间没有空格。)

输出格式

一个整数,表示Bessie到达终点所需的最短时间。

输入输出样例

输入 #1复制

5 6
###=##
#.W.##
#.####
#.@W##
######

输出 #1复制()

3

 题解:本题就是一个相对简单的bfs,但本题还是有一个稍微不同的地方,那就是传送装置,当你踩上传送装置你就会被自动传送到另一端,所以本题你还需要知道每个传送装置的位置,其实也不难只是再需一个双层循环而已。
解题开始,你要先找到人物(‘@’)位置,将它的横纵坐标记录下来,入队并标记,接着开始搜索,通过队首元素,将你能走的所有点的位置和步数都入队并且标记为已走过(每个点的步数为他队首元素的步数+1),如果是传送装置则需要将传送装置的另一端位置入队,直到走到终点结束搜索。

#include <stdio.h>
char a[310][310],book[310][310];//book用来标记点是否走过
int n,m;
struct queue
{int x;
 int y;
 int step; 
}p[100000];
main()
{scanf("%d%d",&n,&m);
 for(int i=0;i<n;i++)
  scanf("%s",a[i]);
 int flag=0,startx,starty,head=0,tail=0; 
 for(int i=0;i<n;i++)
  { for(int j=0;j<m;j++)
     if(a[i][j]=='@') //找出起始位置 
      {book[i][j]=1;
       startx=i;
	   starty=j; 
       flag=1;
       break;
      }
    if(flag) break;
  }
 p[tail].x=startx;//起始位置入队 
 p[tail].y=starty;
 p[tail++].step=0;
 while(head<tail)
 {int zb[4][2]={{0,1},{1,0},{0,-1},{-1,0}},px,py;//四个方向 
  for(int i=0;i<4;i++)
  { px=p[head].x+zb[i][0];
    py=p[head].y+zb[i][1];
    if(px<0||px>n-1||py<0||py>m-1)
	 continue;
	if(a[px][py]>='A'&&a[px][py]<='Z')//转送装置 
	 {flag=0;
	  for(int k=0;k<n;k++)
	  {for(int j=0;j<m;j++)
	    if(a[k][j]==a[px][py]&&(k!=px||j!=py))//传送装置另一端 
	    {px=k;
	     py=j;
	     flag=1;
	     break;
	     }
	   if(flag) break;
	   }
	  p[tail].x=px;//将传送装置另一端入队 
	  p[tail].y=py;
	  p[tail++].step=p[head].step+1;
	 }
	 else if(a[px][py]!='#'&&!book[px][py])//将草地或者终点入队 
	 {book[px][py]=1;
	  p[tail].x=px;
	  p[tail].y=py;
	  p[tail++].step=p[head].step+1;
	 }
	if(a[px][py]=='=') //到达终点 
	{printf("%d",p[tail-1].step);
	 return 0; 
	  }  
  } 
  head++;
 } 
} 

11、P2895 [USACO08FEB]Meteor Shower S

题目描述

Bessie hears that an extraordinary meteor shower is coming; reports say that these meteors will crash into earth and destroy anything they hit. Anxious for her safety, she vows to find her way to a safe location (one that is never destroyed by a meteor) . She is currently grazing at the origin in the coordinate plane and wants to move to a new, safer location while avoiding being destroyed by meteors along her way.

The reports say that M meteors (1 ≤ M ≤ 50,000) will strike, with meteor i will striking point (Xi, Yi) (0 ≤ Xi ≤ 300; 0 ≤ Yi ≤ 300) at time Ti (0 ≤ Ti  ≤ 1,000). Each meteor destroys the point that it strikes and also the four rectilinearly adjacent lattice points.

Bessie leaves the origin at time 0 and can travel in the first quadrant and parallel to the axes at the rate of one distance unit per second to any of the (often 4) adjacent rectilinear points that are not yet destroyed by a meteor. She cannot be located on a point at any time greater than or equal to the time it is destroyed).

Determine the minimum time it takes Bessie to get to a safe place.

输入格式

* Line 1: A single integer: M

* Lines 2..M+1: Line i+1 contains three space-separated integers: Xi, Yi, and Ti

输出格式

* Line 1: The minimum time it takes Bessie to get to a safe place or -1 if it is impossible.

题意翻译

贝茜听说了一个骇人听闻的消息:一场流星雨即将袭击整个农场,由于流星体积过大,它们无法在撞击到地面前燃烧殆尽,届时将会对它撞到的一切东西造成毁灭性的打击。很自然地,贝茜开始担心自己的安全问题。以 Farmer John 牧场中最聪明的奶牛的名誉起誓,她一定要在被流星砸到前,到达一个安全的地方(也就是说,一块不会被任何流星砸到的土地)。如果将牧场放入一个直角坐标系中,贝茜现在的位置是原点,并且,贝茜不能踏上一块被流星砸过的土地。 根据预报,一共有 M 颗流星 (1≤M≤50,000) 会坠落在农场上,其中第i颗流星会在时刻 Ti​ (0≤Ti​≤1,000) 砸在坐标为 (Xi​,Yi​) (0≤Xi​≤300,0≤Yi​≤300) 的格子里。流星的力量会将它所在的格子,以及周围 4 个相邻的格子都化为焦土,当然贝茜也无法再在这些格子上行走。

贝茜在时刻 0 开始行动,它只能在第一象限中,平行于坐标轴行动,每 1 个时刻中,她能移动到相邻的(一般是 4 个)格子中的任意一个,当然目标格子要没有被烧焦才行。如果一个格子在时刻 t 被流星撞击或烧焦,那么贝茜只能在 t 之前的时刻在这个格子里出现。 贝西一开始在(0,0)。

请你计算一下,贝茜最少需要多少时间才能到达一个安全的格子。如果不可能到达输出 -1。

Translated by @奆奆的蒟蒻 @跪下叫哥

输入输出样例

输入 #1复制

4
0 0 2
2 1 2
1 1 2
0 3 5

输出 #1复制

5

 题解:本题依旧用的是bfs,不过不同的地方就是障碍物,障碍物并非一直存在,而是从某个时间点才开始存在,并且它会向四周蔓延一个单位(不越界就行),而解题就是需要找一个最近的没有障碍物的地方。
首先需要将整个地图都赋为一个特别值,例如-1,以便到时候找安全点(需要注意的是不可以因为节约时间直接将整个地图赋为0,因为陨石掉落时间可能为0,我就是在这错了一个点)。然后将陨石掉落的地方和他们蔓延的地方赋值为它们掉落的时间,这里又有一个需要注意的点:如果有多个陨石掉落到同一个地方或者蔓延到同一个地方,只需将最早的那个时间赋值给该点。然后就是通过bfs入队出队搜索到第一个值为-1的点(因为开始已经将所有点都赋成了-1,而后面陨石掉落的地方又被覆盖了,所以值仍为-1的点就是安全点),而搜索时只要没越界,并且你到达那点的时间早于那点陨石掉落的时间或者是安全地并且没被走过的点就能走(即将该点入队)。

#include <stdio.h>
int a[310][310],book[310][310];
int zb[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
struct queue
{int x;
 int y;
 int time; 
}p[1000000];
main()
{for(int i=0;i<310;i++) //先将所有土地变成负数以便到时候找安全地带 
  for(int j=0;j<310;j++)//注意不能让它为0不动,因为流行坠落时间可能为0,我就因为图方便没改,在这W了一个
   a[i][j]=-1; 
 int m,head=0,tail=0;
 scanf("%d",&m);
 for(int i=1;i<=m;i++)
 {int x,y,t;
  scanf("%d%d%d",&x,&y,&t);
  if(a[x][y]!=-1&&a[x][y]>t) a[x][y]=t; //该点被不止一颗陨石砸,则存最早砸的时间 
  else if(a[x][y]==-1) //该点只有一颗陨石砸
   a[x][y]=t;
  for(int j=0;j<4;j++) //陨石烧焦四周土地
  { int px=x+zb[j][0];
    int py=y+zb[j][1];
	if(px<0||py<0) continue;
	if(a[px][py]!=-1&&a[px][py]>t) a[px][py]=t; //该点被不止一团火蔓延,则存最早蔓延的时间 
     else if(a[px][py]==-1) //该点只被一团火蔓延
	 a[px][py]=t; 
  } 
 }
  p[tail].x=0;
  p[tail].y=0;
  p[tail++].time=0;
  book[0][0]=1;
  while(head<tail)
  {for(int i=0;i<4;i++)
   {int px=p[head].x+zb[i][0];
    int py=p[head].y+zb[i][1];
    if(px<0||py<0) continue;
    if((p[head].time+1<a[px][py]||a[px][py]==-1)&&!book[px][py]) //能在陨石掉下前到达或者该地没有陨石掉落 
    {book[px][py]=1;
     p[tail].x=px;
     p[tail].y=py;
	 p[tail++].time=p[head].time+1; 
	}
    if(a[px][py]==-1)//到达安全位置
	{ printf("%d",p[tail-1].time);
	  return 0;
	 } 
	}
	head++;
   }
   printf("-1");//因为return缘故,所以不能到达则会输出-1
} 

 12、P1019 [NOIP2000 提高组] 单词接龙

题目背景

注意:本题为上古 NOIP 原题,不保证存在靠谱的做法能通过该数据范围下的所有数据。

题目描述

单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如 beast 和 astonish,如果接成一条龙则变为 beastonish,另外相邻的两部分不能存在包含关系,例如 at 和 atide 间不能相连。

输入格式

输入的第一行为一个单独的整数 n 表示单词数,以下 n 行每行有一个单词,输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在。

输出格式

只需输出以此字母开头的最长的“龙”的长度。

输入输出样例

输入 #1复制

5
at
touch
cheat
choose
tact
a

输出 #1复制

23

说明/提示

样例解释:连成的“龙”为 atoucheatactactouchoose

n≤20

 题解:本题可使用dfs,找到以龙头开始的字符串开始组成最长的龙,本题并不是很难,但是因为很多地方用的字符串和字符串函数很容易出现问题,我也是想思路写代码十分钟Debug四个小时。其中也总结了一些细节如strlen()返回值为无符号类型,尽量别与负数作比较,还有就是用scanf输字符肯容易出现bug,本题我本来用字符变量存“龙头”的结果样例和错误样例都对一提交给我全红,后来改用了字符串存龙头那一个字符就对了,我也是不太清楚为什么。本题还需要注意的点就是每个单词最多用两次,所以不是单纯的用数组记录单词使用状态而是要存它的使用次数,我是用一个数组暂存“龙”的,所以为了方便回溯我又开了一个数组暂存未连接时的“龙”,到时候回溯直接将它copy回去就行了。

#include <stdio.h>
#include <string.h>
int n,max=0,book[25]={0};//book存各字符串使用次数 
char a[25][1000];
int fun(char *s1,char *s2,int *l) //判断是否可连接,并找出连接部分 
{int k,t,min=999999;
  for(k=0;s1[k];k++)
  {int m=0;
   if(s1[k]==s2[m])
   {for(t=k;s1[t]&&s2[m];t++,m++)
     if(s1[t]!=s2[m]) break;
    if(s1[t]=='\0'&&s2[m]!='\0'&&m<min) min=m; //找最短重合部分 
	}
  }
  if(min==strlen(s1)||min==strlen(s2)||min==999999) return 0; //包含关系或者没有重合部分 
  else 
  {*l=min; //从何处开始连接(即跳过重合部分) 
   return 1;
  }
}
void dfs(char *b)
{if(strlen(b)>max) 
 max=strlen(b);
 for(int i=0;i<n;i++)//遍历每个字符串 
 {int len;
   if(fun(b,a[i],&len)&&book[i]<2)//能连接且使用次数小于两次 
    {char c[10000]={0};
	 strcpy(c,b);//暂存未连接起来的数组,以便回溯 
	 strcat(b,a[i]+len);
     book[i]++;//用的次数加一 
     dfs(b);
     book[i]--;
     strcpy(b,c);//回溯 
	}
 }
}
main()
{char ch[1000]={0};
 scanf("%d",&n);
 for(int i=0;i<n;i++)
  scanf("%s",a[i]);
 scanf("%s",&ch); 
 for(int i=0;i<n;i++)
  if(a[i][0]==ch[0])//以给定字母开头为龙,不知为何直接用字符变量接收字符不行 
   {char b[10000]={0};
    book[i]++;
    strcat(b,a[i]);
    dfs(b);
    book[i]=0;//回溯 ,因为b数组每次都是重新开辟所以没必要回溯 
   }
 printf("%d\n",max);
}

13、P2392 kkksc03考前临时抱佛脚 

题目背景

kkksc03 的大学生活非常的颓废,平时根本不学习。但是,临近期末考试,他必须要开始抱佛脚,以求不挂科。

题目描述

这次期末考试,kkksc03 需要考 4 科。因此要开始刷习题集,每科都有一个习题集,分别有 s1​,s2​,s3​,s4​ 道题目,完成每道题目需要一些时间,可能不等(A1​,A2​,…,As1​​,B1​,B2​,…,Bs2​​,C1​,C2​,…,Cs3​​,D1​,D2​,…,Ds4​​)。

kkksc03 有一个能力,他的左右两个大脑可以同时计算 2 道不同的题目,但是仅限于同一科。因此,kkksc03 必须一科一科的复习。

由于 kkksc03 还急着去处理洛谷的 bug,因此他希望尽快把事情做完,所以他希望知道能够完成复习的最短时间。

输入格式

本题包含 5 行数据:第 1 行,为四个正整数 s1​,s2​,s3​,s4​。

第 2 行,为 A1​,A2​,…,As1​​ 共 s1​ 个数,表示第一科习题集每道题目所消耗的时间。

第 3 行,为 B1​,B2​,…,Bs2​​ 共 s2​ 个数。

第 4 行,为 C1​,C2​,…,Cs3​​ 共 s3​ 个数。

第 5 行,为 D1​,D2​,…,Ds4​​ 共 s4​ 个数,意思均同上。

输出格式

输出一行,为复习完毕最短时间。

输入输出样例

输入 #1复制

1 2 1 3		
5
4 3
6
2 4 3

输出 #1复制

20

说明/提示

1≤s1​,s2​,s3​,s4​≤20。

1≤A1​,A2​,…,As1​​,B1​,B2​,…,Bs2​​,C1​,C2​,…,Cs3​​,D1​,D2​,…,Ds4​​≤60。

 题解:本题可以用动态规划解决,但奈何咱们实力不允许,DP真是烂的一塌糊涂,所以只能用dfs来解决。本题目共有四门科目,每个科目的题数和复习用时不一样,我们要分科目dfs,分别求出每个科目的最少用时,最后再求总和。
具体步骤:我们需要模拟一下dfs过程,我们有左脑右脑可用,那么每次就是先全左脑,再左右脑并用,最后再全右脑。接下来举一个具体的搜索过程,就拿科目四来举例:
1、全部左脑完成。这样耗时为 2+4+3=9。
2、左脑完成1、2题,右脑完成3题。这样耗时是 max(2+4, 3)=6。
3、左脑完成1、3题,右脑完成2题。这样耗时是 max(2+3, 4)=5。
4、左脑完成2 3题,右脑完成1题。这样耗时是 max(4+3, 2)=7。
5、左脑完成1题,右脑完成2、3题。这样耗时是 max(2, 4+3)=7。
6、左脑完成2题,右脑完成1、3题。这样耗时是 max(4, 3+2)=5。
7、左脑完成3题,右脑完成1、2题。这样耗时是 max(3, 2+4)=6。
8、全部右脑完成。这样耗时为 2+4+3=9。
因此第四个科目的最小时间为 5 分钟。

#include <stdio.h>
int s[4],a[4][25],l,r,min;//s科目数,a每科题目,min每科复习最少用时 
int max(int a,int b)
{ if(a>b) return a;
  else return b;
}
void dfs(int i,int step)//i科目下标,step每科题目下标 
{if(step>=s[i])//本科目所有题复习完 
 { if(min>max(l,r)) min=max(l,r);
   return;
 }
  //左脑 
  l+=a[i][step];
  dfs(i,step+1);
  l-=a[i][step];
  //右脑
  r+=a[i][step]; 
  dfs(i,step+1);
  r-=a[i][step];
}
main()
{int sum=0;
 for(int i=0;i<4;i++)
  scanf("%d",&s[i]);
 for(int i=0;i<4;i++)
  for(int j=0;j<s[i];j++) 
   scanf("%d",&a[i][j]);
 for(int i=0;i<4;i++)
  {l=0;r=0;//左右脑用时归零 
   min=999999999;//重新赋值为下一科目做准备
   dfs(i,0);
   sum+=min;//每科用时累加 
  }
 printf("%d",sum);
 return 0;    
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值