题意就是给你一张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;
}