可怕的宇宙射线(dfs/bfs+剪枝)

问题描述

\hspace{17pt}众所周知,瑞神已经达到了CS本科生的天花板,但殊不知天外有天,人外有苟。在浩瀚的宇宙中,存在着一种叫做苟狗的生物,这种生物天生就能达到人类研究生的知识水平,并且天生擅长CSP,甚至有全国第一的水平!但最可怕的是,它可以发出宇宙射线!宇宙射线可以摧毁人的智商,进行降智打击!
\hspace{17pt}宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的左右45°方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂n次,每次分裂后会在分裂方向前进aia_i个单位长度。
\hspace{17pt}现在瑞神要带着他的小弟们挑战苟狗,但是瑞神不想让自己的智商降到普通本科生zjm那么菜的水平,所以瑞神来请求你帮他计算出共有多少个位置会被"降智打击"

Input

输入第一行包含一个正整数n,(n<=30)n,(n<=30),表示宇宙射线会分裂n次。
第二行包含n个正整数a1,a2,,ana_1,a_2,\cdots,a_n,第ii个数ai(ai5)a_i(a_i\le 5)表示第ii次分裂的宇宙射线会在它原方向上继续走多少个单位长度。

Output

输出一个数ans,表示有多少个位置会被降智打击。

Sample input

4
4 2 2 3

Sample output

39

数据范围

数据点 n
10% <=10
40% <=20
100% <=30

时间与内存限制 1000ms 2632144KB

样例解释

下图描绘了样例中宇宙射线分裂的全过程,仅做参考
在这里插入图片描述

解题思路

\hspace{17pt}这个题很明显是一个搜索类问题,使用dfs还是bfs都可以,但是要注意的是剪枝的重要性。这个题n的数据最大达到了30,如果每次分裂的点都进行搜索的话,时间复杂度是O(2n)O(2^n),绝对顶不住,只能拿到40分。那我们想要100分就需要剪枝操作。
\hspace{17pt}剪枝的方法有很多种,关键是如何找到正确的方法,我最开始的想法是,如果当前点被搜索过,并且搜索这个点的时候,方向和向后需要搜索的距离一致,就剪掉。这样一个node节点存储的就是横座标x,纵座标y,方向dir,距离dis。没错,这就是我最开始的想法,所以我的结果如下:
在这里插入图片描述
\hspace{17pt}静下心来仔细思考一下,就知道错在哪里了,挺明显的,有可能以后搜索到这个位置,搜索的距离和方向一致,但是下一步搜索的方向和距离和最开始标记的有可能不同了,这样就剪枝过度了,造成部分位置的丢失。
\hspace{17pt}后来的思路是,仅仅剪掉这一层搜索的时候,重复的位置(也要保证方向相同才剪掉),因为最终搜索的范围是300*300这样的区间,但是当搜索深度很大的时候,分裂的节点必定有很多重复,这样做到了剪枝操作,并且没有重复节点的丢失。node结构体中存储的dis就换成了当前搜索的层数fl(floor的简写)。
\hspace{17pt}我的代码使用的是bfs搜索,利用两个map来存储当前节点是否搜索过和当前节点在当层此方向是否搜索过,利用队列来保存当前要搜索的直线路径的起点。和传统bfs不同的是,这种题目需要两个层次之间分离,可以做一个标记表明队列中弹出的节点是哪一层,也可以直接用两个队列来回倒。我使用的是后者,当前层次的直线起始点放在qdir中,下一层的放在qdir2中,当qdir为空,则将qdir2中的数据放在qdir中(其实这个样子还增加了一定的时间,推荐使用标记标出层次)。更加详细的解析见代码注释。

完整代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <climits>
#include <algorithm>
#include <queue>
#include <vector>
#include <map>
using namespace std;

struct node
{
    int x,y;//当前节点
    int dir;//当前节点搜索的方向,从1-8分别为上,右上,右,右下,下,左下,左,左上
    int fl;//当前所属于的层数
    node(int _x,int _y,int _dir,int _fl):x(_x),y(_y),dir(_dir),fl(_fl){}
    bool operator<(const node &a) const
    {
        if(x<a.x) return true;
        else if(x==a.x){
            if(y<a.y) return true;
            else if(y==a.y){
                if(dir<a.dir) return true;
                else if(dir==a.dir){
                    if(fl<a.fl) return true;
                }
            }
        }
        return false;
    }
};
int n,a[31],ans;
int dx[]={0,1,1,1,0,-1,-1,-1};
int dy[]={1,1,0,-1,-1,-1,0,1};
map<node,bool> mp;//判断当前节点是否搜索过(包含方向和距离),判断该节点是否应该再次搜索
map<long long ,bool> mp2;//单纯记录当前节点是否搜索过,使用long long是直接用x*1000+y来哈希一下
queue<node> qdir;//存储下一层的所有起始节点
queue<node> qdir2;
void bfs()
{
    node start(150,149,0,1);
    //假设初始节点位置为(150,149),向着上方前进,层数为1(注意,(150,149)这个点是不要的,是从这里开始的,这样整个射线的起点就是(150,150)了)
    //解释:本体数据量不大,从初始节点开始,向上下左右最多走150格,默认座标是平面直角座标,左下角为(0,0)
    qdir.push(start);

    for (int i=2; i<=n+1; i++)//a[1]生成的节点要走的距离是a[2],所以从i=2开始
    {
        while(!qdir.empty())
        {
            node lattice=qdir.front(); qdir.pop();
            for (int j=1; j<=a[lattice.fl]; j++)//有了起始节点后,向后搜索a[fl]个节点
            {
                if(!mp2[(lattice.x+j*dx[lattice.dir])*1000+(lattice.y+j*dy[lattice.dir])]){//当前节点没有搜索过
                    mp2[(lattice.x+j*dx[lattice.dir])*1000+(lattice.y+j*dy[lattice.dir])]=true; ans++;
                }
            }
            if(i!=n+1)//最后一次不用分裂了,直接跳出就行了
            {
                //开始分裂成两个节点
                node newnode1(lattice.x+a[i-1]*dx[lattice.dir],lattice.y+a[i-1]*dy[lattice.dir],(lattice.dir+1)%8,i);
                node newnode2(lattice.x+a[i-1]*dx[lattice.dir],lattice.y+a[i-1]*dy[lattice.dir],(lattice.dir+7)%8,i);
                if(!mp[newnode1]){
                    mp[newnode1]=true; qdir2.push(newnode1);
                }
                if(!mp[newnode2]){
                    mp[newnode2]=true; qdir2.push(newnode2);
                }
            }
        }
        while(!qdir2.empty()){
            qdir.push(qdir2.front());qdir2.pop();
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin>>n;
    for (int i=1; i<=n; i++)
        cin>>a[i];

    bfs();

    cout<<ans<<endl;
    return 0;
}

关于此题的优秀博客链接

传送门 这个大佬使用的剪枝方法是对称剪枝,利用的dfs只向右搜索,我提交后发现时间比我的要快3倍,空间少6倍。这个应该才是正解。
传送门 这个文章里面提到的和我的方法类似,不过他是使用的dfs,可以参考。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章