【HNOI模拟题】物理

该博客讨论了一道HNOI模拟题,涉及一维坐标系中n个点的最短路径问题。初始思路是通过单源最短路解决,但因数据规模大可能导致超时。50分算法采用O(n^2)的BFS,而满分算法则利用排序和数据结构(如线段树和单调队列)优化,将时间复杂度降低到O(nlog2n),以适应n=250000的情况。

题目大意

给出一维坐标系上的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;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值