zoukankan      html  css  js  c++  java
  • 【HDU 4771 Stealing Harry Potter's Precious】BFS+状压

    2013杭州区域赛现场赛二水。。。

    类似“胜利大逃亡”的搜索问题,有若干个宝藏分布在不同位置,问从起点遍历过所有k个宝藏的最短时间。

    思路就是,从起点出发,搜索到最近的一个宝藏,然后以这个位置为起点,搜索下一个最近的宝藏,直至找到全部k个宝藏。有点贪心的感觉。

    由于求最短时间,BFS更快捷,但耗内存,这道题就卡在这里了。。。

    这里记录了我几次剪枝的历史。。。题目要求内存上限32768KB,就差最后600KB了。。。但我从理论上觉得已经不能再剪了,留下的结点都是盲目式搜索必然要访问的结点。

    在此贴上剪枝到33292KB的代码,注释里说明了剪掉的部分(剪枝策略可能还有不正确的地方,仅供参考)

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <queue>
     4 using namespace std;
     5 const int MAX_N=100;
     6 int n,m,k;
     7 int sx,sy;
     8 int dx[]={0,0,1,-1},dy[]={1,-1,0,0};
     9 char G[MAX_N+2][MAX_N+2];
    10 int cur_step;
    11 
    12 struct Node
    13 {
    14     int x,y,step,g;//每个点的坐标、走到这里的步数、到这一点已找到的宝藏数
    15     Node(){}
    16     Node(int xx,int yy,int ss,int gg):x(xx),y(yy),step(ss),g(gg){}
    17 };
    18 
    19 int bfs()
    20 {
    21     int get=0;//全局已找到的宝藏数,小于这个数的结点视为“过期”
    22     queue<Node> que;
    23     que.push(Node(sx,sy,0,0));
    24     cur_step=-1;//全局当前的步数
    25     while(!que.empty())
    26     {
    27         Node cur=que.front();
    28         que.pop();
    29         //当此点步数小于等于到达某一个宝藏的步数(即和这个宝藏在搜索树中“同层”),剪枝
    30         if(cur.step<=cur_step&&cur.g<get) continue;
    31         int has=0;//这一点的四个可扩展结点中有没有是宝藏的
    32         for(int i=0;i<4;i++)
    33         {
    34             int nx=cur.x+dx[i];
    35             int ny=cur.y+dy[i];
    36             if(nx<1||ny<1||nx>n||ny>m) continue;
    37             if(G[nx][ny]=='#') continue;
    38             if(cur.g<get) continue;//宝藏数过期
    39             if(G[nx][ny]=='*')//是宝藏
    40             {
    41                 has++;
    42                 sx=nx;
    43                 sy=ny;
    44             }
    45         }
    46         if(has)
    47         {//一旦四个结点有一个是宝藏,则只将这一个入队,由于没有设vis数组!!!同层其他宝藏会在之后被推入队列的
    48             cur.g++;
    49             get=cur.g;
    50             G[sx][sy]='.';
    51             cur_step=cur.step+1;
    52             if(get==k) return cur.step+1;
    53             que.push(Node(sx,sy,cur.step+1,cur.g));
    54             continue;
    55         }
    56         //当四个结点都不是宝藏时,根据盲目式搜索策略,只好全部推入队列,就在这里推入了很多无用但又无法提前预知的点
    57         for(int i=0;i<4;i++)
    58         {
    59             int nx=cur.x+dx[i];
    60             int ny=cur.y+dy[i];
    61             if(nx<1||ny<1||nx>n||ny>m) continue;
    62             if(G[nx][ny]=='#') continue;
    63             if(cur.g<get) continue;
    64             que.push(Node(nx,ny,cur.step+1,cur.g));
    65         }
    66     }
    67     if(get<k) return -1;
    68 }
    69 
    70 int main()
    71 {
    72     //freopen("1002.txt","r",stdin);
    73     while((scanf("%d%d",&n,&m)!=EOF)&&(!(n==0&&m==0)))
    74     {
    75         for(int i=1;i<=n;i++)
    76         {
    77             getchar();
    78             for(int j=1;j<=m;j++)
    79             {
    80                 scanf("%c",&G[i][j]);
    81                 if(G[i][j]=='@')
    82                 {
    83                     sx=i;
    84                     sy=j;
    85                 }
    86             }
    87         }
    88         scanf("%d",&k);
    89         for(int i=0;i<k;i++)
    90         {
    91             int x,y;
    92             scanf("%d%d",&x,&y);
    93             G[x][y]='*';//宝藏标记为*
    94         }
    95         printf("%d
    ",bfs());
    96     }
    97     return 0;
    98 }
    BFS 剪枝 无状压

    就在无计可施时,队友建议我改用DFS,但目测会超时或爆栈;而且总觉得原思路是对的,问题在优化上。

    这时重新看题,发现我居然忽略了一个条件k<=4~~~~~~~~~~有经验的人大概早就会想到状压了,然而我经验还太少,并是没想到,想到了也写不出来。。。

    之后找题解,发现很多是用BFS求出起点和宝藏的最短路,然后再DFS或枚举所有路径。并是没有想通这个思路。

    再找,终于发现有和我相同思路的了,感谢原作者~ http://blog.csdn.net/u011932355/article/details/40819317 于是参照这篇把自己的改成了状压版,然后,就得到了内存和时间都少了一个数量级的结果。。。状压大法好~

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <queue>
     4 using namespace std;
     5 const int MAX_N=100;
     6 int n,m,k;
     7 int sx,sy,goal;
     8 int dx[]={0,0,1,-1},dy[]={1,-1,0,0};
     9 int b[]={1,2,4,8,16};//原作者的写法,这个数组有大用~
    10 char G[MAX_N+2][MAX_N+2];
    11 int vis[MAX_N+2][MAX_N+2][20];//第三维是状压,表示当前已得宝藏状态 1111~0000
    12 char str[105];
    13 
    14 struct Node
    15 {
    16     int x,y,step,get;//走到这一点的步数,到这一点的已得宝藏状态
    17     Node(){}
    18     Node(int xx,int yy,int ss,int gg):x(xx),y(yy),step(ss),get(gg){}
    19 };
    20 
    21 int bfs()
    22 {
    23     memset(vis,0,sizeof(vis));
    24     queue<Node> que;
    25     que.push(Node(sx,sy,0,0));
    26     vis[sx][sy][0]=1;
    27     while(!que.empty())
    28     {
    29         Node cur=que.front();
    30         que.pop();
    31         for(int i=0;i<4;i++)
    32         {
    33             int nx=cur.x+dx[i];
    34             int ny=cur.y+dy[i];
    35             if(nx<1||ny<1||nx>n||ny>m) continue;
    36             if(G[nx][ny]=='#') continue;
    37 
    38             Node next(nx,ny,cur.step+1,cur.get);
    39             if(G[nx][ny]<=8) next.get|=G[nx][ny];//找到一处宝藏,追加到当前宝藏状态
    40             if(vis[nx][ny][next.get]) continue;//同一点同一宝藏状态,只入队一次
    41             vis[nx][ny][next.get]=1;
    42             if(next.get==goal) return next.step;//找到全部宝藏,返回
    43             que.push(next);
    44         }
    45     }
    46     return -1;
    47 }
    48 
    49 int main()
    50 {
    51     //freopen("1002.txt","r",stdin);
    52     while(scanf("%d%d",&n,&m)==2)
    53     {
    54         if(n==0&&m==0) break;
    55         for(int i=1;i<=n;i++)
    56         {
    57             scanf("%s",str);
    58             int cnt=1;
    59             for(int j=0;str[j]!='';j++)
    60             {
    61                 if(str[j]=='#') G[i][cnt++]='#';
    62                 else if(str[j]=='.') G[i][cnt++]='.';
    63                 else if(str[j]=='@')
    64                 {
    65                     G[i][cnt++]='@';
    66                     sx=i;
    67                     sy=cnt-1;
    68                 }
    69             }
    70         }
    71         scanf("%d",&k);
    72         goal=b[k]-1;//通过k和b数组得到目标状态(1111,111,11,1,0之一)
    73         while(k--)
    74         {//按先后顺序,把每个宝藏所在点的状态设为(1,10,100,1000之一)
    75             int x,y;
    76             scanf("%d%d",&x,&y);
    77             G[x][y]=b[k];
    78         }
    79         printf("%d
    ",bfs());
    80     }
    81     return 0;
    82 }

    这个状压是把当前的宝藏访问状态作为vis数组的第三维存入,这样用vis数组对于我来说是第一次,而我的无状压版本大概就是因为没设vis数组,所以本身就增加了很多无用节点的推入,取出后再丢弃就不叫剪枝了。。。因为还是被推入队列了。。。

    比赛时一直纠结一道题是我的不对,感谢两位队友的指教。

    场下这样的过程还是比较利于学习的,关键是在纠结的过程中暴露了自己哪些基础知识还不清楚,给自己提供了补习的方向。

  • 相关阅读:
    rider中的gitbash如何从上次退出的目录启动
    docker swarm外部验证负载均衡时不生效
    css做一个可以变成关闭图标的菜单按钮
    mac外接键盘HOME,END键问题
    kubernetes实践录 使用Baget部署一个私有Nuget仓库
    C#中正确的实现IDisposable接口以释放非托管资源
    网络磁盘nfs使用笔记
    EFCore分组查询(GroupBy)后获取第一个元素
    Angular踩坑ExpressionChangedAfterItHasBeenCheckedError异常
    写了个适用于vscode的minio图床客户端插件 vscodeminiopicman
  • 原文地址:https://www.cnblogs.com/helenawang/p/4910129.html
Copyright © 2011-2022 走看看