Codeforces Volcanoes

地址链接:
http://codeforces.com/problemset/problem/383/B

题意

给你一个N*N的矩阵地图,图上有M座火山,要你输出从(1,1)走到(n,n)需要的步数。
你只能走右或走下,并且不能走火山。如果走不到输出-1。
1<=n<=10的9次方,1<=m<=10的5次方

例子:
6 6
1 2
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

这里写图片描述

解法部分

 采用每次处理一行火山的做法

建一个结构体表示火山,包含行X,列Y
建一个结构体来表示区间,包含左L、右R

处理一行的区间,开两个数组,last、now。last表示正在处理的这一行的上一行可行区间,now表示正在处理的行扩展后的区间。

-1的情况
若是now是组大小为0则走不下去返回false;
若是处理到的行到n行(最后一行),则判断last数组最后一个区间的右是否大于最后一个火山,大于可行,小于等于不可行

若第一行有火山要先处理,因为第一行是从1,1开始,而其他行是从上一行开始,相当于第一行起点只有1个,其他行起点有n个
第一行有火山则last可行区间为1到第一个火山的列Y-1,并将第一行火山过滤(i++,i是遍历火山的变量)
判断接下来的火山是否在第二行,如果不是第二行,那么改变last可行区间为1-n,因为第二行无火山,所有都可以走
若是第一没有火山则last可行区间为1-n

遍历所有火山一行一行处理
先将一个火山和last第一个区间进行比较
若是火山小于区间的L,则跳过这个火山判断下一个
若是火山大于区间的R,拓展区间从区间的L到火山的Y-1
若是火山位于区间内
火山位于区间的头(火山的Y等于区间的L)直接修改区间的L,并查看下一个火山
火山位于区间其他地方,扩展区间now,L等于last区间的L,R等于火山的Y-1,修改last区间的L
(避免扩展重合的区间,找到火山后将区间的L改为火山的Y+1,改完后判定区间的L若是大于R(区间内不可能存在其他火山了),则判断下一个区间)
若是所有区间都尝试玩扩展扔不能扩展,则说明火山(或多个火山)在这一行的最后,否则将扩展区间last,L等于当前尝试last区间的L,R等于n
将now数组的值覆盖掉last数组(用于下一次扩展)
重复上述步骤直到所有火山都处理完,遇到-1的情况返回false,若过程中未返回false则是可行的图
要注意一下区间合并的问题:
例如出现1-3,2-4要把这两个区间合并成一个区间1-4

代码部分

#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;
//fstream fin("1.txt");
//streambuf *buf = cin.rdbuf(fin.rdbuf());//用于重定项输入改成,把cin当成fin
//FILE *buf = freopen("1.txt", "a+", stdin);//用于重定项文件输入,scanf也能用

const int inf = 1 << 29;
const int MAXN = 100010;

struct Node
{
    int r;//行
    int c;//列
};
struct Section
{
    int l;
    int r;
};

Node node[MAXN];
Section last[MAXN], now[MAXN];
int cnt1, cnt2;
int n;
int m;

bool cmp(Node a, Node b)
{
    if (a.r < b.r)
        return true;
    else if (a.r == b.r && a.c < b.c)
        return true;
    else
        return false;
}

int Union(int cnt2)
{
    int t = 1;
    last[t].l = now[1].l;
    last[t].r = now[1].r;
    for (int i = 2; i <= cnt2; i++)
    {
        if (now[i].l <= last[t].r)
            last[t].r = now[i].r;
        else
        {
            t++;
            last[t].l = now[i].l;
            last[t].r = now[i].r;
        }
    }
    return t;
}

bool judge()
{
    cnt1 = cnt2 = 0;
    int i = 1;
    if (node[1].r == 1)//第一行有火山
    {
        cnt1++;
        last[cnt1].l = 1;
        last[cnt1].r = node[1].c - 1;
        while (node[i].r == node[1].r && i <= m)//过滤掉第一行的火山
            i++;
        if (i > m)//若是所有火山都在第一行则可到达(n,n)点
            return true;
        if (node[i].r > node[1].r + 1)//第二行没有火山则可行区间为1-n
        {
            last[cnt1].l = 1;
            last[cnt1].r = n;
        }
    }
    else//第一个火山不是出现在第一行
    {
        cnt1++;
        last[cnt1].l = 1;
        last[cnt1].r = n;
    }
    while (i <= m)
    {
        int now_r = node[i].r;
        if (now_r == n)//判断到最后一行火山,比较最后一个火山和倒二行可行区间右端
        {
            if (node[m].c < last[cnt1].r)//可行区间右端大于最后一个火山所在点,可以到达(n,n)
                return true;
            else
                return false;
        }
        int k = 1;//区间索引
        cnt2 = 0;
        while (node[i].r == now_r && i <= m && k <= cnt1)
        {
            while (node[i].c < last[k].l && node[i].r == now_r && i <= m)//过滤掉位于区间左侧的火山不需要判断
                i++;
            if (i > m)//所有火山都处理结束
                return true;
            if (node[i].r > now_r)//now_r这一行火山都在可行区间左端
                break;
            else if (node[i].c > last[k].r)//火山位于区间的右侧,拓展区间
            {
                cnt2++;
                now[cnt2].l = last[k].l;
                now[cnt2].r = node[i].c - 1;
                while (k <= cnt1 && last[k].r <= node[i].c)//找到火山后面的区间
                    k++;
            }
            else//火山在区间内
            {
                if (node[i].c > last[k].l)//火山不在区间的左头部,拓展区间
                {
                    cnt2++;
                    now[cnt2].l = last[k].l;
                    now[cnt2].r = node[i].c - 1;
                }
                last[k].l = node[i].c + 1;//将区间可行范围移动到火山后
                if (last[k].l > last[k].r)//判断区间是否都已判断完毕
                    k++;
                i++;
            }
        }
        if (k <= cnt1)//扩展区间
        {
            cnt2++;
            now[cnt2].l = last[k].l;
            now[cnt2].r = n;
        }
        if (cnt2 == 0)//无法扩展区间也就是走不下去了,无路可走
            return false;
        while (node[i].r == now_r && i <= m)//过滤掉这一行用不掉的火山
            i++;
        if (i <= m)
        {
            if (node[i].r > now_r + 1)//间隔2行
            {
                cnt1 = 1;
                last[cnt1].l = now[1].l;
                last[cnt1].r = n;
            }
            else
            {
                //将now数组覆盖掉last数组
                cnt1 = Union(cnt2);
            }
        }
    }
    return true;
}
int main()
{
    cin >> n;
    cin >> m;
    int result = 2 * (n - 1);
    for (int i = 1; i <= m; i++)
    {
        cin >> node[i].r;
        cin >> node[i].c;
    }
    sort(node + 1, node + m + 1, cmp);
    if (judge())
        cout << result << endl;
    else
        cout << -1 << endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值