Codeforces-383.B.Volcanoes

题意就是给你一张N*N的图,有M个火山口是不可通过的。从1.1原点到N*N这个点,每次只能向右或向下。问最短路径。不能走到则输出-1

实际上这题只要知道能不能走到N*N就行了,能走到最短路长就是2*(N-1),因为向右向下,而且也只有这个长度了。

所以关键在于怎么判断他能不能走到N*N这个点。做的时候就很快想到了区间一遍遍更新的办法。从第一行到第N行,每次更新可行区间,直到最后一行的区间的最右边包括N这个点就说明可到达输出路长。

可是呢。还是断断续续好几天再A掉,因为平时都在浪~~

关键是那个区间要怎么更新,步骤大致如下:

先对火山口进行一次排序,按照行排然后再按照列排,方面下面一行行去判断

需要一个全局区间,专门记录上一行的可行区间,然后局部函数记录当前遍历的行的区间,比如 6列的矩阵中第3行有2个点分别是 3.2 3.4 那就有区间1-1 3-3 5-6三个有效区间,然后和上一次记录的区间合并更新。你需要注意的是,题意只能向右或者向下,所以区间更新的时候左起点要以上一次的区间的左起点为中心。具体怎么合并有几个可能,纸上画一画就知道条件判断的依据了,合并后如果全局的有效区间长度大于1那没问题,继续走,否则返回-1,因为这一行合并后没有共同区间,所以不会在往下走了,肯定到不了终点

还有个最重要需要优化的地方就是,比如有很多连续行都没有火山口,有效区间就是1-N,不用想,既然能走到这一行,那么全局的有效区间的右端点也能到N,左区间上面说过以上一次的区间左起为准,所以有效区间只要改改右边端点就行了。

上代码,,这题A的间隔时间有点长,代码越写越挫。见谅~
上一点自己DUBUG的数据6 6
1 2
2 5
3 1
4 3
5 6
6 4
->10

6 5
2 5
3 1
4 3
5 6
6 4
->10

6 6
2 3
2 5
3 2
3 4
4 1
4 6
->-1

6 6
2 1
2 2
4 3
4 5
5 4
5 6
->-1

6 5
4 4
4 5
5 5
5 6
6 4
->-1

1000000000 1
999 999
->1999999998


#define _CRT_SECURE_NO_DEPRECATE
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<fstream>
#include<math.h>
#include<algorithm>
#include<stack>
#include<queue>
using namespace std;

const int MAX = 100009;

struct  P//记录火山口的行列信息
{
    int r, c;
}map[MAX];


bool cmp(P a, P b)//按行排序后按列排序
{
    if (a.r < b.r)
        return true;
    else if (a.r == b.r && a.c < b.c)
    {
        return true;
    }
    else
        return false;
}

int N, M;

struct Section//区间信息
{
    int l, r;
}st[MAX];
int L = 0;//全局区间

void combine(Section s1[], int L1, Section s2[], int L2)//区间合并
{
    int rL = 0;
    int j = 0;
    for (int i = 0; i < L2; i++)
    {
        if (s2[i].l > s2[i].r)
            continue;
        if (s1[j].l >= s2[i].l && s1[j].l <= s2[i].r)
        {
            s2[i].l = s1[j].l;
            continue;
        }

        if (s1[j].l >= s2[i].r && s1[j].l <= s2[i].r)
        {
            s2[i].l = s1[j].l;
        }

        if (s2[i].r < s1[j].l)
        {
            s2[i].l = -1;
            s2[i].r = -1;
            continue;
        }

        if (j>=1 && s2[i].l > s1[j - 1].r && s2[i].r < s1[j].l)
        {
            s2[i].l = -1;
            s2[i].r = -1;
            continue;
        }

        if (j == L1 - 1 && s2[i].l > s1[j ].r)
        {
            s2[i].l = -1;
            s2[i].r = -1;
            continue;
        }

        if (L1 == 1 && s2[i].l > s1[j].r)
        {
            s2[i].l = -1;
            s2[i].r = -1;
            continue;
        }
        while (s1[j].r < s2[i].l || s2[i].l > s1[j].r)
        {
            j++;
            i--;
        }
    }
    int m = 0;
    //合并完成后要更新全局区间,S1传进来就是st全局,有点难看!当时想的复杂
    //而且我是通过上一次的区间更新当前正判断行的区间后,再更新全局的
    for (int i = 0; i < L2; i++)
    {
        if (s2[i].l != -1)
        {
            s1[m].l = s2[i].l;
            s1[m++].r = s2[i].r;
        }
    }
    L = m;
}

void solve()
{
    int mi = 1;
    int i = 1;
    Section st2[MAX];
    memset(st2, 0, sizeof(st2));
    int st2L = 0;
    int index = 1;
    L = 0;
    st[L].l = 1;
    if (map[mi].r == 1)
    {
        st[L++].r = map[mi].c - 1;
        i++;
        while (map[mi].r != i && map[mi].r < i)
        {
            mi++;
        }
    }
    else
    {
        st[L++].r = N;
    }
    if (st[L - 1].r == 0)
    {
        L = 0;
        return;
    }
    while (mi < M + 1)//只要去合并有火山口的那行的行了
    {
        if (mi <= M &&  map[mi].r == i)
        {
            if (map[mi].c != 1)
            {
                if (map[mi].c > index)
                {
                    st2[st2L].l = index;
                    st2[st2L++].r = (map[mi].c - 1) >= index ? map[mi].c - 1 : index;
                }
            }
            index = map[mi].c + 1;
            mi++;
            continue;
        }
        if (index <= N)
        {

            st2[st2L].l = index;
            st2[st2L++].r = N;
        }
        combine(st, L, st2, st2L);
        st2L = 0;
        index = 1;
        if (L == 0)
            return;
        bool bb = false;
        bool bb2 = false;
        while (map[mi].r != i && map[mi].r > i + 1)//跳过连续的无火山口的区间
        {
            if (!bb)//第一次你需要更新一下
            {
                st2[st2L].l = index;
                st2[st2L++].r = N;
                L = 0;
                st[L++].r = N;//这时候全局的右起点为N,但左边不变
                st2L = 0;
                bb = true;
            }
            index = 1; 
            if (!bb2)
            {
                bb2 = true;
                i = map[mi].r - 2;//这是优化的地方,直接跳过中间那些连续的五火山口
            }
            i++;
        }
        i++;
    }
    bool bb2 = false;
    while (i <= N)
    {
        if (index <= N)
        {
            st2[st2L].l = index;
            st2[st2L++].r = N;
        }
        combine(st, L, st2, st2L); 
        if (i<N-3 && !bb2)//直接跳过中间那些连续的五火山口
        {
            bb2 = true;
            i = N - 2;
        }
        if (L == 0)
            return;
        st2L = 0;
        index = 1;
        i++;
    }
}

int main()
{
    while (scanf("%d %d", &N, &M) != EOF)
    {
        bool is = false;
        for (int i = 1; i <= M; i++)
        {
            scanf("%d %d",&map[i].r,&map[i].c);
            if (!is && map[i].r == N && map[i].c == N)
            {
                is = true;
            }
        }

        sort(map + 1, map + M + 1, cmp);
        solve();
        if (L!= 0 && st[L - 1].r >= N)
        {
            cout << 2 * (N - 1) << endl;
        }
        else
        {
            cout << -1 << endl;
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值