解题报告-洛谷SCP2025T3 P14255 列车(train)

P14255 列车(train)

题目描述

洛咕咕王国是咕咕星球上领土最辽阔的王国,因此在洛咕咕王国中如何高效移动成为了一个难题。洛咕咕王国有 \(n\) 座城市,神奇的是这 \(n\) 座城市在一条直线上,我们把第 \(i\) 座城市在直线上的位置记作 \(p_i\)。这些城市是按照顺序进行编号的, 因此 \(p_1<p_2<\dots<p_n\)

已知洛咕咕王国有一条按照顺序连接所有城市的铁路,洛咕咕王国在这条铁路上开行了多趟列车以满足各城市间的运输需求。起初对于每一对正整数对 \((i,j)\)\(1\le i<j\le n\)),都有一趟以城市 \(i\) 为起点站、开往城市 \(j\) 且以城市 \(j\) 为终点站的列车。除开起点站和终点站外,这趟列车还会依次停靠中途城市 \(i+1,i+2,\dots,j-1\)

为了减轻洛咕咕王国票务系统的压力,洛咕咕王国开行的所有列车的收费标准实行一票制。洛咕咕咕民只要乘坐从以城市 \(i\) 为起点站、城市 \(j\) 为终点站的列车,票价均为起点与终点位置之差 \(p_j-p_i\),与洛咕咕咕民实际乘坐的区间无关。

洛咕咕王国接下来会按照顺序发生 \(m\) 次事件,第 \(i\) 次事件为以下两种类型之一:

  1. 洛咕咕国王命令停开所有起点站城市编号大于等于 \(x_i\) 终点站编号小于等于 \(y_i\) 的列车。一旦一趟列车在某次事件中被停开,它在后续所有时刻都视作已停开,不会被恢复。
  2. 一位洛咕咕咕民查询搭乘一趟未停开的列车从城市 \(x_i\) 搭乘至城市 \(y_i\) 的最小花费,若不存在这样的列车则输出 -1。若一趟列车先后停靠城市 \(l\) 和城市 \(r\) 两个站点,则称可以搭乘这趟列车从城市 \(l\) 到城市 \(r\),一趟列车的起点站和终点站也算入这趟列车的停靠范围。

输入格式

本题包含多组测试数据。

第一行包含一个正整数 \(T\),表示数据组数。

接下来包含 \(T\) 组数据,每组数据的格式如下:

  • 第一行包含两个正整数 \(n,m\),表示城市个数和事件次数。
  • 第二行包含 \(n\) 个正整数 \(p_1,p_2,\dots,p_n\),表示各个城市在直线上的位置。
  • 接下来 \(m\) 行每行表示一次事件的发生。首先读入一个正整数 \(o\) 表示事件类型:
    • \(o=1\) 表示发生了一个类型 1 事件,接下来两个正整数 \(x_i,y_i\) 表示停开所有起点站城市编号大于等于 \(x_i\) 终点站编号小于等于 \(y_i\) 的列车。
    • \(o=2\) 表示发生了一个类型 2 事件,接下来两个正整数 \(x_i,y_i\) 表示查询搭乘一趟未停开列车从城市 \(x_i\) 搭乘至城市 \(y_i\) 的最小花费。

输出格式

对于每组数据:输出若干行,对于每个类型 2 事件输出一行一个整数表示答案:若存在对应的列车可以搭乘则输出最小花费,否则输出 -1

输入输出样例 #1

输入 #1

2
4 6
1 2 3 4
2 1 3
2 3 4
1 2 3
2 2 3
1 1 4
2 1 4
5 5
1 4 5 7 1000000000
1 2 4
2 3 5
2 2 3
1 1 2
2 3 4

输出 #1

2
1
2
-1
999999995
4
6

说明/提示

【样例 1 解释】

在第一组测试数据中,最初共有 \(6\) 趟列车开行。

  • \(1\) 个事件:查询从城市 \(1\) 搭乘至城市 \(3\) 的最小花费。当前所有列车均在开行,因此最优的方案是搭乘以城市 \(1\) 为起点站、城市 \(3\) 为终点站的列车,花费为 \(p_3-p_1=3-1=2\)
  • \(2\) 个事件:查询从城市 \(3\) 搭乘至城市 \(4\) 的最小花费。当前所有列车均在开行,因此最优的方案是搭乘以城市 \(3\) 为起点站、城市 \(4\) 为终点站的列车,花费为 \(p_4-p_3=4-3=1\)
  • \(3\) 个事件:停开所有起点站城市编号大于等于 \(2\),终点站城市编号小于等于 \(3\) 的列车,即停开以城市 \(2\) 为起点站、城市 \(3\) 为终点站的列车。
  • \(4\) 个事件:查询从城市 \(2\) 搭乘至城市 \(3\) 的最小花费。由于以城市 \(2\) 为起点站、城市 \(3\) 为终点站的列车已被停开,所以无法搭乘这趟列车。最优方案之一是搭乘以城市 \(1\) 为起点站、城市 \(3\) 为终点站的列车,花费为 \(p_3-p_1=3-1=2\),可以证明不存在花费更小的方案。
  • \(5\) 个事件:停开所有起点站城市编号大于等于 \(1\),终点站城市编号小于等于 \(4\) 的列车,即停开所有列车。以城市 \(2\) 为起点站、城市 \(3\) 为终点站的列车先前已被停开,本次事件将不会对这趟列车产生任何影响。
  • \(6\) 个事件:查询从城市 \(1\) 搭乘至城市 \(4\) 的最小花费。由于所有列车已被停开,无法从城市 \(1\) 搭乘至城市 \(4\) ,故输出 -1

对于第二组测试数据,我有一个绝佳的解释,但是这里空间太小写不下。

【样例 2】

见选手目录下的 train/train2.intrain/train2.ans

该组样例满足测试点 \(1\) 的限制。

【样例 3】

见选手目录下的 train/train3.intrain/train3.ans

该组样例满足测试点 \(4\) 的限制。

【样例 4】

见选手目录下的 train/train4.intrain/train4.ans

该组样例满足测试点 \(8\) 的限制。

【样例 5】

见选手目录下的 train/train5.intrain/train5.ans

该组样例满足测试点 \(11\) 的限制。

【样例 6】

见选手目录下的 train/train6.intrain/train6.ans

该组样例满足测试点 \(13\) 的限制。

【样例 7】

见选手目录下的 train/train7.intrain/train7.ans

该组样例满足测试点 \(15\) 的限制。

【样例 8】

见选手目录下的 train/train8.intrain/train8.ans

该组样例满足测试点 \(19\) 的限制。

【样例 9】

见选手目录下的 train/train9.intrain/train9.ans

该组样例满足测试点 \(23\) 的限制。

【数据范围】

对于所有测试数据,保证:\(1\leq T\leq 10\)\(2\leq n,m\leq 10^5\)\(1\leq x<y\leq n\)\(1\leq p_1<p_2<\dots<p_n\leq 10^9\)

测试点编号 \(n,m\leq\) 特殊性质
\(1\sim 3\) \(100\)
\(4\sim 7\) \(3000\) ^
\(8\sim10\) \(5\times 10^4\) A
\(11,12\) ^ B
\(13,14\) ^ C
\(15\sim 18\) ^ D
\(19\sim 22\) ^ E
\(23\sim 25\) \(10^5\)

若第 \(i\) 次事件为类型 1,则令 \(S_i\) 为第 \(i\) 次事件所有被停开的列车(不一定是本次事件后才被停开的列车)所构成的集合。

特殊性质 A:保证不存在正整数 \(i,j\) 满足 \(1\leq i<j\leq m\) 且第 \(i\) 次事件为类型 2,第 \(j\) 次事件为类型 1。

特殊性质 B:保证不存在正整数 \(i,j\) 满足 \(1\leq i<j\leq m\) 且第 \(i\) 次事件和第 \(j\) 次事件均为类型 1 且 \(S_i\cap S_j\neq \varnothing\)

特殊性质 C:保证不存在正整数 \(i,j\) 满足 \(1\leq i<j\leq m\) 且第 \(i\) 次事件和第 \(j\) 次事件均为类型 1 且 \(S_i\nsubseteq S_j\)

特殊性质 D:对于每次事件,均保证 \(x_i,y_i\) 在所有可能的 \(x_i,y_i\) 中等概率选取。

特殊性质 E:保证 \(p_n=n\)


解题报告

好吧,连数据结构题都不会写了……

比赛时被第二题卡住了,赛后也没有想到具体怎么写。

很重要的一步就是设出 \(f_i\),表示从城市 \(i\) 可以到达的最近城市。

这个 \(f_i\) 有两个很重要的性质:

  • \(i\) 一定可以通过列车到达 \(f_i\) 及其之后的点。

这个性质还是比较显然的,假设 \(i\) 可以到达 \(f_i\) 但不能到达的 \(f_j(f_j >f_i)\),那么就至少有一条指令停开了运行在区间 \([i,f_j]\) 或其母集。那么区间 \([i,f_i]\) 肯定也被停开了,矛盾。

  • 序列 \(f_i\) 是一个不降序列。

假设有任意两个点 \(i\)\(j\),满足 \(i <j\)\(f_i>f_j\),那么运行在区间 \([i+1,f_i]\) 的列车肯定被停开了,又有 \([i+1,f_i] \supset [j,f_j]\),那么运行在区间 \([j,f_j]\) 同样被停开,于是 \(j\) 无法到达 \(f_j\),矛盾。

假设我们已经知道了所有 \(f_i\),如何求出从 \(l\)\(r\) 的最小费用?

首先找到在区间 \([1,l]\) 的点最后一个满足中 \(f_i \leq r\) 的点 \(pos\)

从区间 \([1,pos]\) 的点经过点 \(l\) 到达点 \(r\)\(r\) 作为终点,最优解为 \(\min_{x \in [1,pos]} (p_r-p_x)=p_r-p_{pos}\)

从区间 \([x+1,pos]\) 的点经过点 \(l\) 到达点 \(r\)\(r\) 作为中间点,最优解为 \(\min_{x \in [pos+1,l]} (p_{f_x}-p_x)\)

显然可以用线段树和二分维护。

同时,考虑如何用线段树修改 \(f_i\)。假设现在要停开运行在区间 \([l,r]\) 的列车,由于 \(f_i\) 不降,只需要在区间 \([l,r]\) 二分出最后一个满足 \(f_i \leq r+1\) 的点 \(pos\),把在区间 \([l,pos]\) 中的点的 \(f_i\) 全部覆盖为 \(r+1\) 就可。

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF=(1E+18)+10;
const int N=201100;

#define ckmax(x,y) ( x=max(x,y) )
#define ckmin(x,y) ( x=min(x,y) )

inline int read()
{
	int f=1,x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch))  { x=x*10+ch-'0';    ch=getchar(); }
	return f*x;
}

int n,m,p[N];

#define iL i<<1
#define iR i<<1|1
struct node
{
    int val,ans;
    int tag;
}T[N<<2];

inline void pushup(int i)
{ T[i].ans=min(T[iL].ans,T[iR].ans); }

inline void Node(int i)
{
    T[i].val=T[i].ans=0;
    T[i].tag=0;
}

void build(int i,int l,int r)
{
    Node(i);
    if(l==r)
    {
        T[i].ans=p[l+1]-p[l];
        T[i].val=l+1;
        return ;
    }
    int mid=l+r>>1;
    build(iL,l,mid);
    build(iR,mid+1,r);
    pushup(i);
}

inline void pushdown(int i,int l,int r)
{
    if(!T[i].tag) return ;
    int mid=l+r>>1;
    T[iL].ans=p[T[i].tag]-p[mid];
    T[iL].val=T[iL].tag=T[i].tag;
    T[iR].ans=p[T[i].tag]-p[r];
    T[iR].val=T[iR].tag=T[i].tag;
    T[i].tag=0;
}

void update(int i,int l,int r,int L,int R,int x)
{
    if(L<=l && r<=R)
    {
        T[i].ans=p[x]-p[r];
        T[i].val=T[i].tag=x;
        return ;
    }
    pushdown(i,l,r);
    int mid=l+r>>1;
    if(L<=mid) update(iL,l,mid,L,R,x);
    if(R>mid)  update(iR,mid+1,r,L,R,x);
    pushup(i);
}

int query_val(int i,int l,int r,int pos)
{
    if(l==r) return T[i].val;
    pushdown(i,l,r);
    int mid=l+r>>1;
    if(pos<=mid) return query_val(iL,l,mid,pos);
    if(pos>mid)  return query_val(iR,mid+1,r,pos);
}

inline int calc(int l,int r,int x)
{
    int ans=-1;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(query_val(1,1,n,mid)<=x)
          l=mid+1,ans=mid;
        else
          r=mid-1;
    }
    return ans;
}

int query_ans(int i,int l,int r,int L,int R)
{
    if(L>R) return INF;
    if(L<=l && r<=R) return T[i].ans;
    pushdown(i,l,r);
    int mid=l+r>>1,tmp=INF;
    if(L<=mid) ckmin(tmp,query_ans(iL,l,mid,L,R));
    if(R>mid)  ckmin(tmp,query_ans(iR,mid+1,r,L,R));
    return tmp;
}

signed main()
{
	freopen("train.in","r",stdin);
	freopen("train.out","w",stdout);
    int tQ=read();
    while(tQ--)
    {
        n=read(),m=read();
        for(int i=1;i<=n;i++) p[i]=read();
        p[n+1]=INF;build(1,1,n);
        // 一定要多设一个 p[n+1],要不然计算代价时会出现负值
        while(m--)
        {
            int opt=read(),l=read(),r=read();
            if(opt==1)
            {
                int pos=calc(l,r,r+1);
                if(~pos) update(1,1,n,l,pos,r+1);
            }
            else
            {
                int pos=calc(1,l,r),ans=INF;
                if(~pos) ans=p[r]-p[pos];
                else pos=0;
                ckmin(ans,query_ans(1,1,n,pos+1,l));
                if(ans>=INF/2) puts("-1");
                else printf("%lld\n",ans);
            }
        }
    }
	return 0;
}
posted @ 2025-10-20 20:10  南北天球  阅读(28)  评论(0)    收藏  举报