zoukankan      html  css  js  c++  java
  • 搜索:POJ2251&POJ1426&POJ3087&POJ2488

    图的遍历也称为搜索,就是从图中某个顶点出发,沿着一些边遍历图中所有的顶点,且每个顶点仅被访问一次,遍历可采取两种不同的方式:深度优先搜索(DFS)和广度优先搜索(BFS)。

    1.DFS算法思想`

         从顶点v出发深度遍历图G的算法

        ① 访问v0顶点,置vis[v0]=1,搜索v0未被访问的邻接点w,若存在邻接点w,则dfs(w),直到到达所有邻接点都被访问过的顶点u为止,接着退回一步,看是否还有其他没有被访问的邻接点。如果有,则访问此顶点,进行前述类似的访问,如果没有,就在退回一步进行搜索,重复上述过程直到所有顶点都被访问过。

       ② dfs算法最大特色就在于其递归特性,使得算法代码简洁。

    2. BFS算法思想

        从顶点v出发广度遍历图G的算法

        ①  访问v0顶点,置vis[v0]=1,依次访问顶点v0各个未被访问的邻接点,将v0各个未被访问的邻接点都访问到,分别从这些邻接点出发,依次访问他们未被访问的邻接点,直到所有顶点都被访问过。常用队列实现。

        ② 非递归,好理解。

    基本思想讲完了,下面说说今天做的四道搜索题吧。

    1.POJ2251 Dungeon Master:http://poj.org/problem?id=2251

      题意:给出一三维空间的地牢,要求求出由字符'S'到字符'E'的最短路径

      移动方向可以是上,下,左,右,前,后,六个方向

      每移动一次就耗费一分钟,要求输出最快的走出时间。

      不同L层的地图,相同RC坐标处是连通的

       思路:这道题和之前做的走迷宫什么的类似,就是多了一维,做的时候把每一维的次序弄乱了,调了很久才搞清楚。

    int dx[6]= {0,0,0,0,1,-1};
    int dy[6]= {0,0,1,-1,0,0};
    int dz[6]= {1,-1,0,0,0,0};
    用三个数组分别存x方向,y方向,z方向的操作。我用的BFS,建图的时候建对,就没什么问题了。
    附上代码:
     1 #include<stdio.h>
     2 #include<queue>
     3 #include<string.h>
     4 using namespace std;
     5 int visit[40][40][40];
     6 int step[40][40][40];
     7 int dx[6]= {0,0,0,0,1,-1};
     8 int dy[6]= {0,0,1,-1,0,0};
     9 int dz[6]= {1,-1,0,0,0,0};
    10 int n,m,k,flag;
    11 struct node
    12 {
    13     int x,y,z;
    14 } start,end;
    15 int bfs()
    16 {
    17     flag=0;
    18     queue <node> Q;
    19     Q.push(start);
    20     node temp;
    21     visit[start.x][start.y][start.z]=1;
    22     while(!Q.empty())
    23     {
    24         temp=Q.front();
    25         Q.pop();
    26         if(temp.x==end.x&&temp.y==end.y&&temp.z==end.z)
    27         {
    28             flag=1;
    29             break;
    30         }
    31         int x=temp.x;
    32         int y=temp.y;
    33         int z=temp.z;
    34         for(int i=0; i<6; i++)
    35         {
    36             if(!visit[z+dz[i]][x+dx[i]][y+dy[i]]&&x+dx[i]<m&&x+dx[i]>=0&&y+dy[i]<k&&y+dy[i]>=0&&z+dz[i]<n&&z+dz[i]>=0)
    37             {
    38                 temp.x=x+dx[i];
    39                 temp.y=y+dy[i];
    40                 temp.z=z+dz[i];
    41                 visit[temp.z][temp.x][temp.y]=1;
    42                 Q.push(temp);
    43                 step[temp.x][temp.y][temp.z]=step[x][y][z]+1;
    44             }
    45         }
    46     }
    47     return flag;
    48 }
    49 int main()
    50 {
    51     int i,j,t;
    52     char ch;
    53     while(scanf("%d%d%d",&n,&m,&k)!=EOF)
    54     {
    55         if(n==0&&m==0&&k==0)
    56             break;
    57         memset(step,0,sizeof(step));
    58         getchar();
    59         for(i=0; i<n; i++)
    60         {
    61             for(j=0; j<m; j++)
    62             {
    63                 for(t=0; t<k; t++)
    64                 {
    65                     scanf("%c",&ch);
    66                     if(ch=='.')
    67                         visit[i][j][t]=0;
    68                     else if(ch=='#')
    69                         visit[i][j][t]=1;
    70                     else if(ch=='S')
    71                     {
    72                         visit[i][j][t]=0;
    73                         start.x=j;
    74                         start.y=t;
    75                         start.z=i;
    76                     }
    77                     else if(ch=='E')
    78                     {
    79                         visit[i][j][t]=0;
    80                         end.x=j;
    81                         end.y=t;
    82                         end.z=i;
    83                     }
    84                 }
    85                 getchar();
    86             }
    87             if(i!=n-1)
    88                 getchar();
    89         }
    90         if(bfs())
    91             printf("Escaped in %d minute(s).
    ",step[end.x][end.y][end.z]);
    92         else
    93             printf("Trapped!
    ");
    94     }
    95     return 0;
    96 }

    2.POJ 1426 Find The Multiple  :http://poj.org/problem?id=1426

      题意: 给出n,求由0或1组成的一个整数能整除n。

      思路:我是暴力加bfs水过的,用C++交时间超限了,看到有人说用G++交不会超时,一交果然AC了。。不会优化,先附上丑陋代码:

     1 #include<stdio.h>
     2 #include<queue>
     3 #include<string.h>
     4 using namespace std;
     5 int n;
     6 long long bfs()
     7 {
     8     queue<long long> Q;
     9     while(!Q.empty())
    10     {
    11         Q.pop();
    12     }
    13     Q.push(1);
    14     while(1)
    15     {
    16         long long sum=Q.front();
    17         if(sum%n==0)
    18             return sum;
    19         Q.pop();
    20         Q.push(sum*10);
    21         Q.push(sum*10+1);
    22     }
    23 }
    24 int main()
    25 {
    26     while(scanf("%d",&n)!=EOF&&n)
    27     {
    28         printf("%lld
    ",bfs());
    29     }
    30     return 0;

        看到网上别人思路:

        解题思路:

        首先暴力枚举肯定是不可能的 1000ms 想不超时都难,而且枚举还要解决大数问题。。

        要不是人家把这题放到搜索,怎么也想不到用BFS。。。

        解题方法: BFS+同余模定理

        不说废话

        首先说说朴素的不剪枝搜索方法:

        我以n=6为例

        首先十进制数,开头第一个数字(最高位)一定不能为0,即最高位必为1

        设6的 ”01十进制倍数” 为k,那么必有k%6 = 0

        现在就是要用BFS求k值

        1、先搜索k的最高位,最高位必为1,则此时k=1,但1%6 =1  !=  0

        因此k=1不是所求,存储余数 1

        2、搜索下一位,下一位可能为0,即 k*10+0,此时k=10,那么k%6=4

        可能为1,即 k*10+1,此时k=11,那么k%6=5

        由于余数均不为0,即k=10与k=11均不是所求

        3、继续搜索第三位,此时有四种可能了:

        对于k=10,下一位可能为0,即 k*10+0,此时k=100,那么k%6=4

        下一位可能为1,即 k*10+1,此时k=101,那么k%6=5

        对于k=11,下一位可能为0,即 k*10+0,此时k=110,那么k%6=2

         下一位可能为1,即 k*10+1,此时k=111,那么k%6=3

         由于余数均不为0,即k=100,k=101,k=110,k=111均不是所求

    4、继续搜索第四位,此时有八种可能了:

    对于k=100,下一位可能为0,即 k*10+0,此时k=1000,那么k%6=4

                  下一位可能为1,即 k*10+1,此时k=1001,那么k%6=5

    对于k=101,下一位可能为0,即 k*10+0,此时k=1010,那么k%6=2

                  下一位可能为1,即 k*10+1,此时k=1011,那么k%6=3

    对于k=110,下一位可能为0,即 k*10+0,此时k=1100,那么k%6=2

                  下一位可能为1,即 k*10+1,此时k=1101,那么k%6=3

    对于k=111,下一位可能为0,即 k*10+0,此时k=1110,那么k%6=0

                  下一位可能为1,即 k*10+1,此时k=1111,那么k%6=1

    我们发现k=1110时,k%6=0,即1110就是所求的倍数

    从上面的演绎不难发现,用BFS是搜索 当前位数字 (除最高位固定为1),因为每一位都只有0或1两种选择,换而言之是一个双入口BFS

    本题难点在于搜索之后的处理:对余数的处理,对大数的处理,余数与所求倍数间的关系

    接下来说说处理大数问题和剪枝的方法:

    首先我们简单回顾一下 朴素搜索 法:

    n=6

    1%6=1  (k=1)

     (1*10+0)%6=4  (k=10)

    {

        (10*10+0)%6=4   (k=100)

        {

            (100*10+0)%6=4  (k=1000)

            (100*10+1)%6=5  (k=1001)

        }

        (10*10+1)%6=5  (k=101)

        {

            (101*10+0)%6=2  (k=1010)

            (101*10+1)%6=3  (k=1011)

        }

    }

       (1*10+1)%6=5  (k=11)

    {

        (11*10+0)%6=2   (k=110)

        {

            (110*10+0)%6=2  (k=1100)

            (110*10+1)%6=3  (k=1101)

        }

        (11*10+1)%6=3   (k=111)

        {

            (111*10+0)%6=0  (k=1110)   有解

            (111*10+1)%6=1  (k=1111)  由于前面有解,这个余数不存储

        }

    }

    }

    从上面可以看出余数的存数顺序(逐层存储):

    用数组mod[]存储余数,其中mod[0]不使用,由mod[1]开始

    那么mod中的余数依次为: 1 4 5 4 5 2 3 4 5 2 3 2 3 0  共14个

    即说明我们得到 余数0 之前,做了14步*10的操作,那么当n值足够大的时候,是很容易出现k为大数的情况(事实上我做过统计,200以内的n,有18个n对应的k值为大数

    那么我们再用int去存储k就显得不怎么明智了。

    为了处理所有情况,我们自然会想到 是不是应该要用int[]去存储k的每一位?

    而又由于k是一个01序列,那能不能把 *10得到k每一位的问题 转化为模2的操作得到k的每一位(0或1) 呢?

    答案是可以的

    首先我们利用 同余模定理 对得到余数的方式进行一个优化

    (a*b)%n = (a%n *b%n)%n

    (a+b)%n = (a%n +b%n)%n

    随便抽取上面一条式子为例

    前一步 (11*10+1)%6=2   即k=110 , k%6=2

    当前步 (110*10+1)%6=2

    由同余模定理  (110*10+1)%6 = ((110*10)%6+1%6 )%6 = ((110%6 * 10%6)%6 +1 )%6

    不难发现下划线部分110%6等于 (11*10+0)%6 = 2

    所以当前步(110*10+1)%6可以转变为  (2*10+1)%6=2

    很显然地,这种处理把k=110 等价于 k=2

    即用 前一步操作得到的余数 代替 当前步的k值

    而n在200的范围内, 余数值不可能超过3位数, 这就解决了 大数的问题

    通过这种处理手法,我们只需在BFS时顺手存储一个 余数数组mod[] ,就能通过mod[i-1]得到mod[i]  ,直到mod[i]==0 时结束,大大减少了运算时间

    前面已经提到,n=6时,求余操作进行了14次,对应地,BFS时*10的操作也进行了14次。

    令i=14,通过观察发现,i%2恰好就是 6 的倍数的最低位数字

    i/2  再令 i%2 ,恰好就是 6 的倍数的 次低位数字。。。

    循环这个操作,直到i=0,就能得到 6的 01倍数(一个01队列),倒序输出就是所求

    这样就完成了 *10操作到 %2操作的过渡

    由于n值有限,只是1到200的整数,因此本题也可以用打表做,通过上面的方法得到结果后,就把1~200的倍数打印出来,重新建立一个程序,直接打表就可以了。

    不过打表比上面介绍的方法快不了多少

    代码:

     1 //Memory Time
     2 //2236K  32MS 
     3 
     4 #include<iostream>
     5 using namespace std;
     6 
     7 int mod[524286];  //保存每次mod n的余数
     8                   //由于198的余数序列是最长的
     9                   //经过反复二分验证,436905是能存储198余数序列的最少空间
    10                   //但POJ肯定又越界测试了...524286是AC的最低下限,不然铁定RE
    11 
    12 int main(int i)
    13 {
    14     int n;
    15     while(cin>>n)
    16     {
    17         if(!n)
    18             break;
    19 
    20         mod[1]=1%n;  //初始化,n倍数的最高位必是1
    21 
    22         for(i=2;mod[i-1]!=0;i++)  //利用同余模定理,从前一步的余数mod[i/2]得到下一步的余数mod[i]
    23             mod[i]=(mod[i/2]*10+i%2)%n;
    24                      //mod[i/2]*10+i%2模拟了BFS的双入口搜索
    25                      //当i为偶数时,+0,即取当前位数字为0  。为奇数时,则+1,即取当前位数字为1
    26 
    27         i--;
    28         int pm=0;
    29         while(i)
    30         {
    31             mod[pm++]=i%2;   //把*10操作转化为%2操作,逆向求倍数的每一位数字
    32             i/=2;
    33         }
    34         while(pm)
    35             cout<<mod[--pm];  //倒序输出
    36         cout<<endl;
    37     }
    38     return 0;
    39 }

    3.POJ 3087 Shuffle'm Up:http://poj.org/problem?id=3087

        题意:

    已知两堆牌s1和s2的初始状态, 其牌数均为c,按给定规则能将他们相互交叉组合成一堆牌s12,再将s12的最底下的c块牌归为s1,最顶的c块牌归为s2,依此循环下去。

    现在输入s1和s2的初始状态 以及 预想的最终状态s12

    问s1 s2经过多少次洗牌之后,最终能达到状态s12,若永远不可能相同,则输出"-1"。

       思路:从题意来看,直接模拟就好了,用不到BFS。好吧,我也的确直接用模拟做了,

    附上代码:

        

     1 #include<stdio.h>
     2 #include<string.h>
     3 int main()
     4 {
     5     int t,l,i,j;
     6     char s1[101],s2[101],s[205],s12[205],s3[205];
     7     scanf("%d",&t);
     8     for(j=1; j<=t; j++)
     9     {
    10         int ans=0;
    11         scanf("%d",&l);
    12         getchar();
    13         gets(s1);
    14         gets(s2);
    15         gets(s12);
    16         strcpy(s,s1);
    17         strcpy(s+l,s2);
    18         s3[0]='';
    19         while(strcmp(s3,s12)!=0)
    20         {
    21             for(i=0; i<l; i++)
    22             {
    23                 s3[i*2+1]=s1[i];
    24                 s3[i*2]=s2[i];
    25             }
    26             s3[2*l]='';
    27             ans++;
    28             if(strcmp(s3,s)==0)
    29             {
    30                 ans=-1;
    31                 break;
    32             }
    33             for(i=0; i<l; i++)
    34                 s1[i]=s3[i];
    35             strcpy(s2,s3+l);
    36         }
    37         printf("%d %d
    ",j,ans);
    38     }
    39     return 0;
    40 }

      4.POJ 2488 A Knight's Journey:http://poj.org/problem?id=2488

      题意:

        马从A1开始走“日”,如果能走遍棋盘上所有点,输出路径,否则,输出impossible.

     思路:

        定义了一个结构体变量,存该step时的位置。

    int dir[8][2]={{-2,-1},{-2,1},{-1,-2},{-1,2},{1,-2},{1,2},{2,-1},{2,1}};可以保证最后按字典序输出。
    做的时候还是没按字典序输出,最后发现是行和列搞反了。
    附上代码:
     1 #include<stdio.h>
     2 #include<string.h>
     3 struct node
     4 {
     5     char c;
     6     char z;
     7 }move[900];
     8 int n,m;
     9 int dir[8][2]={{-2,-1},{-2,1},{-1,-2},{-1,2},{1,-2},{1,2},{2,-1},{2,1}};
    10 int visit[30][30];
    11 int flag;
    12 int dfs(int x,int y,int step)
    13 {
    14     int xx,yy,i;
    15     if(step==n*m)
    16     {
    17         flag=1;
    18         return 1;
    19     }
    20     for(i=0; i<8; i++)
    21     {
    22         xx=x+dir[i][0];
    23         yy=y+dir[i][1];
    24         if(xx>=1 && xx<=m&& yy>=1 && yy<=n&& !visit[xx][yy])
    25         {
    26             move[step].c=xx-1+'A';
    27             move[step].z=yy+'0';
    28             visit[xx][yy]=1;
    29             dfs(xx,yy,step+1);
    30             if(flag)
    31                 return 1;
    32             visit[xx][yy]=0;
    33         }
    34     }
    35     return 0;
    36 }
    37 int main()
    38 {
    39     int t,i,k;
    40     scanf("%d",&t);
    41     for(k=1; k<=t; k++)
    42     {
    43         flag=0;
    44         memset(visit,0,sizeof(visit));
    45         memset(move,0,sizeof(move));
    46         scanf("%d%d",&n,&m);
    47         visit[1][1]=1;
    48         move[0].c='A';
    49         move[0].z='1';
    50         printf("Scenario #%d:
    ",k);
    51         if(dfs(1,1,1))
    52         {
    53             for(i=0;i<n*m;i++)
    54             {
    55                 printf("%c%c",move[i].c,move[i].z);
    56             }
    57         }
    58         else
    59             printf("impossible");
    60         printf("
    
    ");
    61     }
    62     return 0;
    63 }

    总结完了。。弱菜要去看昨晚Bestcoder的题了,噶呜~





    ————Anonymous.PJQ
  • 相关阅读:
    Qt环境搭建(Visual Studio)
    HTML基础
    关于Qt
    I am back
    Node Security
    Mobile Assistant
    Home Server
    抉择-什么最重要
    在一个JSP页面中包含另一个JSP页面的三种方式
    JS控制DIV隐藏显示
  • 原文地址:https://www.cnblogs.com/PJQOOO/p/3888899.html
Copyright © 2011-2022 走看看