peiwenjun's blog 没有知识的荒原

P5465 [PKUSC2018]星际穿越 题解

题目描述

\(n\) 个星球,第 \(i\) 个星球和 \([l_i,i-1]\) 中的每个星球之间有一条双向边, \(dis(x,y)\) 为第 \(x\) 个星球到第 \(y\) 个星球的距离。

\(q\) 次询问,给定 \(l,r,x\) ,求 \(\frac{\sum_{i=l}^rdis(x,i)}{r-l+1}\) 的值。

数据范围

  • \(1\le n,q\le 3\cdot 10^5,l\lt r\lt x\) 。

时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{500MB}\) 。

分析

别被骗了,这并不是一道图论题。

性质一:若 \(x\gt y\) ,则 \(\forall y\le z\le x\) ,有 \(dis(x,z)\le dis(x,y)\) 。

废话,无论如何从 \(x\) 走到 \(y\) ,一定会经过中间所有节点。

性质二:若 \(x\gt y\) ,则 \(x\to y\) 的最短路至多只会向右走一次;如果向右走,只可能是第一步走到 \(l_z\lt x\) 的点 \(z\) 。

考虑 \(x\to y\) 路径上编号最大(最靠右)的点。

如果这个点为 \(x\) ,假设中间某一步 \(u\to v\) 向右走,根据性质一, \(x\to u\to v\to y\) 一定没有 \(x\to v\to y\) 优。

如果这个点为 \(z\gt x\) ,那么到达 \(z\) 这一步是向右的。

根据前面的分析,\(z\to y\) 是不会 "绕路" 的,对于 \(x\to z\) 的路径:

  • 如果 \(l_z\le x\) ,显然 \(dis(l_z,x)=1\) 。

  • 如果 \(l_z\gt x\) ,我们证明此时一定不是最短路径。

    由于 \(y\lt x\lt z\) ,所以 \(z\to y\) 时一定经过了某个点 \(z'\) 满足 \(l_{z'}\le x\) ,路径 \(x\to z'\to y\) 一定比 \(x\to z\to y\) 更优。


用上面的结论预处理 \(dis(x,y)\) ,时间复杂度 \(\mathcal O(n^2)\) ,可以获得 \(70pts\) 。

\(dis=1\) 是平凡的。

\(dis=2\) 时,从 \(x\) 出发,能走到的编号最小的点是什么?

可以证明是 \(\min\limits_{l_x\le i\le n}l_i\) ,分第一步向左还是向右讨论一下就行。

进一步,假设 \(k\ge 2\) 步能走到的编号最小的点为 \(y\) ,那么\(k+1\)步能走到的编号最小的点为\(\min\limits_{l_y\le i\le n}l_i\) 。

然后考虑倍增。

\(f_{i,j}\) 为从 \([i,n]\) 出发,跳 \(2^j\) 步能到达的最小编号。

\(g_{i,j}=\sum_{k=f_{i,j}}^{i-1}dis([i,n],k)\) ,其中 \(dis([i,n],k)\) 表示 \([i,n]\) 中某个点到 \(k\) 的距离的最小值。

转移是简单的:

\[f_{i,0}=\min_{i\le k\le n}l_k,f_{i,j}=f_{f_{i,j-1},j-1}\\ g_{i,0}=i-f_{i,0},g_{i,j}=g_{i,j-1}+g_{f_{i,j-1},j-1}+2^{j-1}\cdot(f_{i,j-1}-f_{i,j})\\ \]

注意定义不是从 \(i\) 出发,比如下面这组数据:

7
1 2 1 2 5 3

\(f_{6,1}=1\) ,但是 \(dis(6,1)=3,dis([6,7],1)=2\) 。


再来考虑怎么查询,还是倍增。

首先把询问差分成 \(\sum_{k=l}^{i-1}dis(i,k)-\sum_{k=r+1}^{i-1}dis(i,k)\) 。

先强制跳一步 \(l_i\) ,然后给每个 \(\lt l_i\) 的点的 \(dis\) 都加上 \(1\) ,此后可以把 \([l_i,n]\) 都当成终点!

那么接下来的问题形如 \(\sum_{k=r}^xdis([x,n],k)\) ,这个就可以倍增用g数组求解了。

倒序枚举 \(j\) ,维护已经花费的步数和已经知道最短路的后缀部分的距离和即可。

时间复杂度 \(\mathcal O(n\log n)\) 。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=3e5+5,inf=1e9;
int n,q;
int l[maxn],suf[maxn];
ll f[maxn][19],g[maxn][19];
ll calc(int r,int x)
{
    if(l[x]<=r) return x-r;
    int cnt=1,cur=l[x];
    ll res=x-l[x];
    for(int j=18;j>=0;j--)
        if(f[cur][j]>=r)
        {
            res+=g[cur][j]+(cur-f[cur][j])*cnt;
            cnt+=1<<j,cur=f[cur][j];
        }
    res+=(cur-r)*(cnt+1ll);
    return res;
}
ll gcd(ll a,ll b)
{
    if(!b) return a;
    return gcd(b,a%b);
}
int main()
{
    scanf("%d",&n),suf[n+1]=inf;
    for(int i=2;i<=n;i++) scanf("%d",&l[i]);
    for(int i=n;i>=1;i--) suf[i]=min(suf[i+1],l[i]);
    for(int i=1;i<=n;i++)
    {
        f[i][0]=suf[i],g[i][0]=i-suf[i];
        for(int j=1;j<=18;j++)
        {
            f[i][j]=f[f[i][j-1]][j-1];
            g[i][j]=g[i][j-1]+g[f[i][j-1]][j-1]+((f[i][j-1]-f[i][j])<<(j-1));
        }
    }
    scanf("%d",&q);
    for(int i=1,l,r,x;i<=q;i++)
    {
        scanf("%d%d%d",&l,&r,&x);
        ll up=calc(l,x)-calc(r+1,x),dn=r-l+1,g=gcd(up,dn);
        printf("%lld/%lld\n",up/g,dn/g);
    }
    return 0;
}

posted on 2022-07-15 18:27  peiwenjun  阅读(24)  评论(0)    收藏  举报

导航