搜索专练测试 3.2

打完比赛才知道,我学过搜索吗?


USACO2013 黑洞

问题描述:
李宗泽的爱好是在周末进行物理学实验,但事与愿违,实验将N个黑洞(2 <= N <= 12, N为even)具象化在了他的农场里,每个都有明确的坐标位置。
根据他的计算,李宗泽知道将会形成N/2对连接起来的黑洞。如果黑洞A和B被连成一对,那么任何物体进入黑洞A,将会以进入黑洞A的方向从黑洞B中出来;进入黑洞B,也会以进入时的方向从黑洞A中出来。举例来说,黑洞A在(0,0),黑洞B在(1,0),牛玉鑫从(1/2,0)开始向X轴正方向移动,进入黑洞B,从黑洞A中出来,将继续向X轴正方向移动,再次进入黑洞B,被困在一个循环里。
李宗泽知道每一个黑洞在他的农场上的具体坐标,牛玉鑫只会向X轴正方向移动,但却不知道牛玉鑫目前的位置。
请你帮助李宗泽计算共有多少种黑洞配对方法会使在不幸的位置的牛玉鑫陷入循环。

输入说明:
第一行:一个正整数N;
第二到N+1行:每行两个整数X,Y描述一个黑洞的位置,每个坐标在0..1,000,000,000内。

样例输入:(wormhole.in)
4
0 0
1 0
1 1
0 1
有4个黑洞,形成一个正方形的循环。

输出说明:
第一行:一个数,代表所有的会让牛玉鑫陷入循环的黑洞配对方法数。

样例输出:(file wormhole.out)
2
给这4个黑洞编号为1..4。如果将1和2相连,3和4相连,牛玉鑫从1和2之间或3和4之间出发时会陷入循环。相同的,如果连接1和3,2和4,牛玉鑫也会陷入循环。只有连接1和4,2和3,牛玉鑫从任何一个位置开始移动都不会陷入循环。

数据那么小,暴力嘛。算了一下,n=12的时候最多也只是11*9*7*5*3*1=没多少种 配对方式,枚举无障碍。但是一开始思路被图论拘住了,只想着连边判环,对水平线上右边的黑洞单向建边,配对黑洞之间双向建边,从第一个点开始扫一遍。对于一个点,优先走配对黑洞,如果没有就走右边的单向边居然也骗了50分。

仔细想这种思路是错误的。因为显然每个黑洞都是有配对黑洞的,我那个sb理论根本不成立,只能水个样例,和真实情况完全不一样。它应该是从不同的点开始搜判环,但是我没重敲这个思路,有机会试试。

为什么不敲呢,因为我看见官方绝妙的题解。这个判环真的好巧妙啊,好优雅啊………………
唯一值得欣喜的是,我的配对枚举写得还是很好看的。

#include<bits/stdc++.h>
using namespace std;

int n; 
struct farm_
{
    int x,y;
}a[20];
int next_[20],partner_[20];//next_[i]=i点右边相邻的那个点,partner_[i]=与i匹配的黑洞。
bool flag[20];//是否配对。
int ans=0;


inline void read(int &x)
{
    x=0;int f=1;char s=getchar();
    for(;s<'0'||s>'9';s=getchar()) if(s=='-') f=-1;
    for(;s>='0'&&s<='9';s=getchar()) x=(x<<3)+(x<<1)+s-48;
    x*=f;

}

bool check()//判断是否有环。
{
    for(int i=1;i<=n;++i)
    {
        int x=i;
        for(int j=1;j<=n;++j) 
            x=next_[partner_[x]];//太强了这里,即从i点开始,不断地重复传送黑洞-右行-传送的过程,正常情况下最多n遍,n个点嘛。
        if(x) return true;//如果出现某一次,n遍之后右边仍然有点,说明死循环了。
    }
    return false;
}

void dfs(int x)
{
    if(x==n) {
        if(check()) ++ans;
        return;
    }//配完啦。
    int xx;
    for(int i=1;i<=n;++i)
      if(!flag[i]){
          xx=i;break;
      }//找到第一个没配对的点,这样就可以有效避免重复遗漏,这是我正常测试唯一带脑子的地方了。
    flag[xx]=1;
    for(int i=xx+1;i<=n;++i)
        if(!flag[i])
        {
            flag[i]=1;
            partner_[xx]=i;partner_[i]=xx;
            dfs(x+2);
            partner_[xx]=0;partner_[i]=0;
            flag[i]=0;//回个溯。
        }
    flag[xx]=0;
    return;
}


void init()
{
    read(n);
    for(int i=1;i<=n;++i)  read(a[i].x),read(a[i].y);
}

void work()
{
    memset(next_,0,sizeof(next_));
    memset(partner_,0,sizeof(partner_));
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j) 
            if(a[i].y==a[j].y&&a[i].x<a[j].x&&(a[next_[i]].x-a[i].x>a[j].x-a[i].x||!next_[i]))
                next_[i]=j;//找啊找啊找右手。


    memset(flag,0,sizeof(flag));
    dfs(0);
    printf("%d",ans);
}

int main()
{
    init();
    work();
    return 0;
}

gohome 安全回家

【问题描述】
Farmer John在森林里迷路了,现在他急需要回家。
森林被分成了N*M的矩阵,每个单元格有一个字符来标示属性,’.’表示空地,’V’表示Fj所在的位置,’J’表示Fj家的位置。在森林里有很多危险的地方,危险的地方被标记为’+’。
Fj想要在回家的路上,让自己距离危险的地方尽可能的远,请你帮他设计一条线路,让这条线路上的每个点距离 危险的地方 最近的距离 最大。
假设Fj的位置为 (R,C),某个危险点的位置为 (A,B)Fj距离危险点的距离为:
|R-A| + |C-B|
当然,Fj必须要回家,也许他必须要经过危险的地方。
【输入】
第一行两个整数:N和M (1 ≤ N, M ≤ 500)
接下来N行,每行M个字符,范围是:’.’, ‘+’, ‘V’, ‘J’.
数据保证肯定有’V’, ‘J’,且至少有一个’+’
【输出】
一个整数,表示那个距离

【输入输出样例1】
gohome.in gohome.out
4 4 3
+…
….
….
V..J

【输入输出样例2】
gohome.in gohome.out
4 5 0
…..
.+++.
.+.+.
V+.J+

【数据范围】
30% 数据保证 10,<=N,M<=50

说实话,题目说的那个距离最初惊到我了,我根本不知道它在说什么距离。……幸好我们有万能(假的)的样例。经过手动推演发现题目所求应该是,最小值最大的那条路径上的那个最小值。

好绕。

没关系,可以显然看出,我们要预处理出每个点距离危险位置的最小值,bfs最短路即可,完全没有问题。
至于怎么求解,我是太不熟练了。这种题目的描述应该一眼二分答案。但我考场上写了dfs,T到飞起,让人难过。二分答案的思路很好懂,每次验证一下即可,为了卒我遗志,我还是改了个bfs+bfs的。
要带回溯,长得不伦不类。

#include<bits/stdc++.h>
using namespace std;

int n,m;
int a[600][600];
int sx,sy,tx,ty;
struct que
{
    int x,y;
}q[5000010];
int head,tail;
int s[600][600];
bool flag[600][600]; 
int ans[600][600]={};//走到这个点时我们最小值中的最大值。
int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};


inline void read(int &x)
{
    x=0;int f=1;char s=getchar();
    for(;s<'0'||s>'9';s=getchar()) if(s=='-') f=-1;
    for(;s>='0'&&s<='9';s=getchar()) x=(x<<3)+(x<<1)+s-48;
    x*=f;

}

void first_()
{
    memset(a,1,sizeof(a));
    memset(s,0,sizeof(s));
    memset(ans,0,sizeof(ans)); 
    head=tail=0;
}

void bfs()//预处理,没什么好说的。
{
    while(head++<tail)
    {
        int u=q[head].x,v=q[head].y;
        for(int i=0;i<4;++i)
        {
            int x=u+dx[i],y=v+dy[i];
            if(!a[x][y]&&!flag[x][y])
            {
                flag[x][y]=1;
                s[x][y]=s[u][v]+1;
                q[++tail].x=x;q[tail].y=y;
            }
        }
    }
}

void bfs2()
{
    head=0;tail=0;
    q[++tail].x=sx;q[tail].y=sy;
    flag[sx][sy]=1;
    ans[sx][sy]=s[sx][sy];//起点初始化。
    while(head++<tail)
    {
        int u=q[head].x,v=q[head].y;
        for(int i=0;i<4;++i)
        {
            int x=u+dx[i],y=v+dy[i];
            if(a[x][y]!=0) continue;//越界,打入冷宫。
            if(ans[x][y]<s[x][y]&&ans[x][y]<ans[u][v])////感谢cs的代码支持,我觉得这里的判定也很风骚。ans[x][y]<s[x][y]和ans[x][y]<ans[u][v]都意味着这个点可以更新为更大的。
            {
                ans[x][y]=min(s[x][y],ans[u][v]);//取最小值。它要放在外面,因为纵使这个点已经在队中了,它也可以被更新的。
                if(!flag[x][y])
                {
                    flag[x][y]=1;
                    q[++tail].x=x;q[tail].y=y;
                }
            }
        }
        flag[u][v]=0;//回溯。因为它没有人知道如果下次从别的方向走他会不会更小。
    }
}

void init()
{
    memset(flag,0,sizeof(flag));
    read(n);
    read(m);
    first_();
    char x;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m+1;++j)
        {
            scanf("%c",&x);
            if(x=='.') a[i][j]=0;
            if(x=='V') a[i][j]=0,sx=i,sy=j;
            if(x=='J') a[i][j]=0,tx=i,ty=j;
            if(x=='+') {
                q[++tail].x=i;q[tail].y=j;flag[i][j]=1;//所有危险位置作为起点进队。
            }
        }

}



void work()
{


    bfs();  //预处理。

    memset(a,0,sizeof(a));
    for(int i=1;i<=n;++i) a[i][0]=a[i][m+1]=16843009;
    for(int i=1;i<=m;++i) a[0][i]=a[n+1][i]=16843009;//我风骚而造作的边界处理,让它不越界而已,写这么繁琐是个人恶趣味。

    memset(flag,0,sizeof(flag)); 
    bfs2(); 

    printf("%d",ans[tx][ty]);
}

int main()
{
    init();
    work();
    return 0;
}




我的数组越开越混乱了,q的大小也没有把握,交的时候居然开了5w。嗳,反思吧。


meteor 流星雨

【问题描述】
流星雨是美丽的,但是流星落下来也能砸死人的。
有一大片流星要在海亮教育园的操场落下,而小x恰好在操场数星星。小x面临最大的问题不是浪漫,而是保住小命。
我们把海亮教育园的操场认为是坐标系的第一象限。小x现在位于坐标系的原点。
现在有M颗流星会落在海亮教育园的操场上,其中第i颗流星会在时刻T_i砸在坐标为(X_i, Y_i)的格子里。流星的力量会将它所在的格子,以及周围4个相邻的格子都化为焦土,当然小x也无法再在这些格子上行走,这样他会被烧伤。
小x从0时刻开始逃离,他只能上下左右移动,并且一个时刻只能移动一个格子,当然,这个格子必须是完好的。
现在小x想知道,最少经过多少时刻,他可以到达一个安全的格子。
【输入】
一个正整数M
接下来M行,每行3个整数Xi,Yi和Ti。分别表示第i颗流星落下的坐标和时间。保证所有坐标都在第一象限。
【输出】
一个整数,表示最少逃离时刻。如果逃离不了,那么输出-1,表示小x肯定被流星砸着了。

【输入输出样例1】
meteor.in meteor.out
4 5
0 0 2
2 1 2
1 1 2
0 3 5
【样例说明】
一共有4颗流星将坠落在操场,它们落地点的坐标分别是(0, 0),(2, 1),
(1, 1)以及(0, 3),时刻分别为2,2,2,5。

在t=5时的牧场,离小x最近的安全的格子是(3,0)——不过由于早在第二颗流星落地时,小x直接跑去(3,0)的路线就被封死了。离小x第二近的安全格子为(4,0),但它的情况也跟(3,0)一样。再接下来的格子就是在(0,5)-(5,0)这条直线上。在这些格子中,(0,5),(1,4)以及(2,3)都能在5个单位时间内到达。
【数据范围】
20% M<=25
40% M<=200
100% M<=50000 0 <= X_i <= 300;0 <= Y_i <= 300 0 <= T_i <= 1,000

这才是真的一眼题。初始化染个色,裸的bfs就可以。
然而我写炸了。代码里讲。

#include<bits/stdc++.h>
using namespace std;

int m;
int a[320][320];
bool flag[320][320];
struct que
{
    int x,y;
    int t; //走到这个点的时间。
}q[100000];
int ans=0;
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};

inline void read(int &x)
{
    x=0;int f=1;char s=getchar();
    for(;s<'0'||s>'9';s=getchar()) if(s=='-') f=-1;
    for(;s>='0'&&s<='9';s=getchar()) x=(x<<3)+(x<<1)+s-48;
    x*=f;

}

void bfs()
{
    int head=0,tail=0;
    q[++tail].x=0;q[tail].y=0;q[tail].t=0;
    while(head++<tail)
    {
        int u=q[head].x,v=q[head].y;
        for(int i=0;i<4;++i)
        {
            int x=u+dx[i],y=v+dy[i];
            if(x<0||y<0) continue;
            if(a[x][y]==16843009) {
                printf("%d",q[head].t+1);
                exit(0);
            }//一旦访问到一个安全区域就输出。+1是因为q[head].t是走到head那个点的,要走到这个安全位置还要1个时间单位。
            if(!flag[x][y]&&a[x][y]>q[head].t+1){
                flag[x][y]=1;
                q[++tail].x=x;q[tail].y=y;q[tail].t=q[head].t+1;
            }//我挂掉的原因愚蠢得可笑,我带了个计数器tott,每次扩展点就++,全然忘记同一层是同一时间,大约也是真没带脑子。样例果然不可信。
        }
    }
    printf("-1");exit(0);
} 


void init()
{
    memset(a,1,sizeof(a));
    read(m);
    int x,y,z;
    for(int i=1;i<=m;++i)
    {
        read(x);read(y);read(z);
        a[x][y]=min(a[x][y],z);
        a[x+1][y]=min(a[x+1][y],z);
        if(x) a[x-1][y]=min(a[x-1][y],z);
        a[x][y+1]=min(a[x][y+1],z);
        if(y) a[x][y-1]=min(a[x][y-1],z);//初始化(x,y)最早成为禁区的时间。
    }
}

void work()
{
    memset(flag,0,sizeof(flag));
    flag[0][0]=1;
    bfs();
} 

int main()
{
    init();
    work();
    return 0;
}

T2T3其实都好做,T1我觉得官方题解那种不太好想。但是最后我差不多也就T1有分,该拿的拿不到,近期是不是太浮躁了一点?据说搜索要告一段落开启dp新征程了,但是什么启发式迭代加深几乎都没有敲过题,well,别颓了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值