题目大意
给出一维坐标系上的n个点,和n个点所代表的区间,其中第一个点坐标为0。对于任意两个点,右边的点可以到达左边(包括坐标相同)当且仅当满足它们代表的区间有重合且距离不超过一个给出的值L。求编号2——n的点到第一个点的最短距离。无法到达输出-1
对于50%的数据,3≤n≤20000;
对于100%的数据,1≤n≤250000,0≤xi,yi,li≤2000000000,1≤l≤2000000000,xi≤yi
最初的思路
在1右边的点到达1,也相当于从1出发到其它的点。所以跑个单源最短路是可行的,但是数据范围较大,加上两点连边的数量也可能很大,所以最短路会超时。
50分算法
这道题时间限制比较宽,所以O(n^2)的单源最短路是可以过掉的。对当前的点枚举其它点,判断区间是否重合即可。因为两点之间的距离为1,我们可以直接打个bfs,设dis[i]为1到i的最短距离,转移方程显然
满分算法
n达到250000的情况,bfs会超时。这时我们考虑用数据结构来维护区间。
那么我们可以给区间以li为关键字排序,然后进行插入、查询、删除。
考虑两个问题:
1. 如何用数据结构插入并查询区间;
2. 如何用数据结构删除一个区间;
如果枚举的区间的坐标是从小到大的,那么显然,它们的答案也是不递减的,所以对于线段树的每个节点都开个单调队列来维护最小值,删除时直接删掉队首。
对于一个1..n的范围,在线段树中插入、询问、删除一个区间l..r,区间l..r最多会被分成log(n)块。所以单调对列的总大小不会太大。
考虑一个实现上的问题。如何插入区间能使与它有重合的区间查询到它。
当插入操作到达一个线段树的区间L..R时,如果这log(n)个区间是L..R的一个子区间,我们也把它放进单调队列。
查询的时候,对于这log(n)个区间,在线段树上对应结点的单调队列上取出队首即可。
这时有一个问题,如果一个询问区间是另一个插入区间所对应log(n)个区间的一个子区间,插入的时候,我们到了这log(n)个区间就退出了,那么可能会查询不到。
其实这个问题只要与上面相反地,对于一个插入区间的log(n)个块,我们开另一个单调队列,专门记录这些插入区间,当查询操作到达一个线段树区间L..R,如果这log(n)个区间是L..R的一个子区间,我们就在这个单调队列里查询,就不会有遗漏了。
时间复杂度是O(nlog2n)的
下面附一下代码(不够优美别介意O(∩_∩)O)
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 250005
#define maxm 1048585
#define maxs 26250205
#define INF 2000000005
#define hd head[id]
#define tl tail[id]
#define b_id B[i].id
using namespace std;
int n,l,m,p,tot,Ans[maxn],Interval[maxn][2],Place[maxn];
struct data
{
int x,id,typ;
} A[maxn*2] , B[maxn*2] ;
bool cmp (data a , data b)
{
return a.x < b.x || a.x == b.x && a.typ < b.typ ;
}
struct Monotone_Queue
{
int head[maxm],tail[maxm],pre[maxs],next[maxs],ID[maxs],tot;
void init ()
{
memset ( head , 127 , sizeof ( head ) ) ;
memset ( tail , 255 , sizeof ( tail ) ) ;
memset ( pre , 255 , sizeof ( pre ) ) ;
memset ( next , 127 , sizeof ( next ) ) ;
tot=0;
}
void Insert ( int id , int x )
{
ID[++tot]=x;
for ( ; hd <= tl && Ans[ ID[ tl ] ] >= Ans[x] ; tl=pre[tl]) ;
if ( hd<=tl ) pre[tl = tot] = hd , next[hd] = tot; else hd = tl = tot;
}
void Delete ( int id , int x )
{
if ( ID[hd]==x ) hd=next[hd];
}
int Query ( int id )
{
if (hd <= tl) return Ans[ ID[ hd ] ]; else return INF;
}
} Data1 , Data2 ;
void insert (int l , int r , int a , int b , int id , int x)
{
Data1.Insert( x , id );
if (l == a && r == b)
{
Data2.Insert( x , id );
return ;
}
int mid= (l + r) / 2;
if (b <= mid) insert(l , mid , a , b , id , x * 2);
else if (a > mid) insert(mid+1 , r , a , b , id , x * 2 + 1);
else
{
insert(l , mid , a , mid , id ,x * 2);
insert(mid+1 , r , mid+1 , b , id , x * 2 + 1);
}
}
void Delete (int l , int r , int a , int b , int id , int x)
{
Data1.Delete( x , id );
if (l == a && r == b)
{
Data2.Delete( x , id );
return ;
}
int mid= (l + r) / 2;
if (b <= mid) Delete(l , mid , a , b , id , x * 2);
else if (a > mid) Delete(mid+1 , r , a , b , id , x * 2 + 1);
else
{
Delete(l , mid , a , mid , id ,x * 2);
Delete(mid+1 , r , mid+1 , b , id , x * 2 + 1);
}
}
int query (int l , int r , int a , int b , int x)
{
int mininum=Data2.Query( x );
if ( l == a && r == b ) return min( mininum , Data1.Query( x ) ) ;
int mid = (l + r) / 2;
if (b <= mid) return min( mininum , query(l , mid , a , b , x * 2) ) ;
if (a > mid) return min( mininum , query(mid + 1 , r , a , b , x * 2 + 1) ) ;
return min( min ( mininum , query(l , mid , a , mid , x * 2) ) , query(mid+1 , r , mid+1 , b , x * 2 + 1));
}
void init ()
{
scanf("%d%d",&n , &l);
Interval[0] [0]=0; Interval[0] [1]=INF; Place[0]=0; m=0;
for (int i = 1 ; i < n ; i++) scanf("%d%d%d", &Interval[i] [0] , &Interval[i] [1] , &Place[i]);
for (int i = 0 ; i < n ; i++)
{
A[m].x = Interval[i] [0]; A[m].id = i; A[m++].typ = 0;
A[m].x = Interval[i] [1]; A[m].id = i; A[m++].typ = 1;
}
sort (A , A+m , cmp);
p = 0;
for (int i=0 ; i < m ; i++) Interval[ A[i].id ] [ A[i].typ ] = (!i || A[i].x != A[i-1].x )? ++p : p;
tot = 0;
for (int i = 1 ; i < n ; i++)
{
B[tot].x = Place[i] - l; B[tot].id = i; B[tot++].typ = 0;
B[tot].x = Place[i]; B[tot].id = i; B[tot++].typ = 1;
}
B[tot].x = 0; B[tot].id = 0; B[tot++].typ = 1;
sort (B , B+tot , cmp);
}
void work ()
{
Data1.init();Data2.init();
Ans[0] = 0;
insert ( 1 , p , Interval[0] [0] , Interval[0] [1] , 0 , 1);
for (int i = 0 ; i < tot ; i++)
if ( !B[i].typ )
{
Ans[b_id] = query ( 1 , p , Interval[b_id] [0] , Interval[b_id] [1] , 1) + 1 ;
insert ( 1 , p , Interval[b_id] [0] , Interval[b_id] [1] , b_id , 1) ;
} else
{
Delete ( 1 , p , Interval[b_id] [0] , Interval[b_id] [1] , b_id , 1);
}
for (int i = 1 ; i < n ; i++)
if ( Ans[i] <= n ) printf("%d\n",Ans[i]); else printf("-1\n");
}
int main ()
{
freopen("data1.in","r",stdin);
freopen("T3.out","w",stdout);
init () ;
work () ;
return 0;
}
该博客讨论了一道HNOI模拟题,涉及一维坐标系中n个点的最短路径问题。初始思路是通过单源最短路解决,但因数据规模大可能导致超时。50分算法采用O(n^2)的BFS,而满分算法则利用排序和数据结构(如线段树和单调队列)优化,将时间复杂度降低到O(nlog2n),以适应n=250000的情况。

247

被折叠的 条评论
为什么被折叠?



