LOJ6435【PKUSC2018】星际穿越题解(贪心+倍增)

题目:LOJ6435.
题目大意:给定一张无向图 n n n个点,表示现在又知道了一些信息为每个点 i i i与编号为 [ l i , i − 1 ] [l_i,i-1] [li,i1]的点有连边,边权均为 1 1 1.现在给定 q q q组询问 l i , r i , x i ( l i &lt; r i &lt; x i ) l_i,r_i,x_i(l_i&lt;r_i&lt;x_i) li,ri,xi(li<ri<xi),表示在 [ l i , r i ] [l_i,r_i] [li,ri]中等概率随机选择一个点 y i y_i yi,求 x i x_i xi y i y_i yi最短路长度的期望.设 d i s ( x , y ) dis(x,y) dis(x,y)表示点 x x x到点 y y y的最短路长度,答案为:
1 r i − l i + 1 ∑ j = l i r i d i s ( x i , j ) \frac{1}{r_i-l_i+1}\sum_{j=l_i}^{r_i}dis(x_i,j) rili+11j=liridis(xi,j)

1 ≤ n , q ≤ 3 ∗ 1 0 5 1\leq n,q\leq 3*10^5 1n,q3105.

显然这个期望中的 1 r i − l i + 1 \frac{1}{r_i-l_i+1} rili+11可以算出 x i x_i xi [ l i , r i ] [l_i,r_i] [li,ri]所有点的距离和之后再乘上去,现在问题变为求 x i x_i xi [ l i , r i ] [l_i,r_i] [li,ri]所有点的距离和.

首先一个贪心,每一次的询问 x i x_i xi一定是第一步向前或向后,之后全部向前走.

为什么呢?显然的.因为如果第一步向后走,那么选择一个可以走到的点中,下一步可以走到最前面
点显然是可行的.如果第二步还要往后走的话,由于我们第一步选的点已经是可以走到最前面的了,所以第二步可定选的是第一步走不到的点,而由于这是张无向图,它显然不可能走到比起点 x i x_i xi更前面的点的…

然后又有一个贪心,每一次必然走下一次可以走到最前面的点选择.

显然,这样选择包含其他任何其他选择可以到达的点,肯定是没问题的.

有了这两个性质后,就可以了开始做这道题了.

很自然地设 f [ i ] [ j ] f[i][j] f[i][j]表示点 i i i往前走 j j j步可以到达的最前面的点,预处理出来之后,考虑如何回答询问.

直接统计 x i x_i xi走到 [ l i , r i ] [l_i,r_i] [li,ri]有些麻烦,我们可以利用前缀和的思想,把问题拆为 x i x_i xi走到 [ l i , x i ) [l_i,x_i) [li,xi) ( r i , x i ) (r_i,x_i) (ri,xi)两部分.

之后询问时,第一步的时候分成两种情况,一种是直接从起点开始往前走,一种是先从起点往后走再往前走,最后答案在两种情况中取最小即可.

第一种大概流程如下:
1.设当前走到的最前面的点为 p p p,上一次为 p ′ p&#x27; p,当前走的步数为 n o w now now.
2.从 [ p , p ′ ) [p,p&#x27;) [p,p)中找到一个可以走得最前面的点 b p bp bp,设 b p bp bp可以走到的最前面的点为 n p np np.
3.走到 [ n p , p ) [np,p) [np,p)的步数必然都为 n o w + 1 now+1 now+1,贡献计入答案后,令 p ′ = p , p = n p , n o w = n o w + 1 p&#x27;=p,p=np,now=now+1 p=p,p=np,now=now+1.
4.重复执行上述过程直到 n p ≤ l np\leq l npl(这里的边界要处理一下).

第二种其实只需要求一下 ( x i , n ] (x_i,n] (xi,n] x i x_i xi可以到达的点中下一步走最前面的点就是第一种情况了,就不再重复了.

我们会发现这个过程有些难搞,一次询问复杂度达到了 O ( n ) O(n) O(n),怎么办呢?

由于每一次走的时候取区间 [ p , p ′ ) [p,p&#x27;) [p,p)中最优的决策时,区间的两个端点都不固定并不好搞,但容易发现 [ p ′ , n ] [p&#x27;,n] [p,n]中的点肯定不会走得比 [ p , p ′ ) [p,p&#x27;) [p,p)中的点优,是不是可以操作一下呢?

改变状态 f [ i ] [ j ] f[i][j] f[i][j]的意义,现在改为区间 [ i , n ] [i,n] [i,n]中的点每个点往前走 j j j步可以到达的最前面的点中最小的,也就是说取了一下后缀最小值.

这个时候我们就可以省去每一个区间中最优决策的时间了.然而,这个做法最坏情况下仍然是 O ( n ) O(n) O(n)一次询问.

但是, f [ i ] [ j ] f[i][j] f[i][j]显然是可以倍增预处理的,而且,设 s u m [ i ] [ j ] sum[i][j] sum[i][j]表示 i i i走到 [ f [ i ] [ j ] , i ) [f[i][j],i) [f[i][j],i)的最短距离和, s u m sum sum也是可以倍增预处理的.

具体是这样的:
f [ i ] [ j ] = f [ f [ i ] [ j − 1 ] ] [ j − 1 ] s u m [ i ] [ j ] = s u m [ i ] [ j − 1 ] + s u m [ f [ i ] [ j − 1 ] ] [ j − 1 ] + 2 j − 1 ∗ ( f [ i ] [ j − 1 ] − f [ i ] [ j ] ) f[i][j]=f[f[i][j-1]][j-1]\\ sum[i][j]=sum[i][j-1]+sum[f[i][j-1]][j-1]+2^{j-1}*(f[i][j-1]-f[i][j]) f[i][j]=f[f[i][j1]][j1]sum[i][j]=sum[i][j1]+sum[f[i][j1]][j1]+2j1(f[i][j1]f[i][j])

然后,这个问题就解决了?

不,我们解决的只是第一种,还有第二种.

幸运的是,第二种看起来有些难搞,但其实我们只需要在第一步的时候,强制往前走就可以了.

为什么呢?因为第一步强制往前走了,如果往后走更优,这个决策也被包含在了 ( i , n ] (i,n] (i,n]里面,走第二步的时候已经被顺便统计进去了…

需要注意的是,这个时候我们还要打个特判,如果终点位置只需要从起点开始走一步就直接返回区间长度.

然后这个问题就真的解决了.

惊不惊喜?意不意外?

时间复杂度 O ( ( n + q ) log ⁡ n ) O((n+q)\log n) O((n+q)logn).

代码如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=300000,C=20;

LL gcd(LL a,LL b){return b?gcd(b,a%b):a;}

int n,l[N+9],cq;
int go[N+9][C];
LL sum[N+9][C];

void Pre_doubly(){
  go[n][0]=l[n];
  for (int i=n-1;i>=1;--i)
    go[i][0]=min(l[i],go[i+1][0]),sum[i][0]=(LL)i-go[i][0];
  for (int i=1;i<=n;++i)
    for (int j=1;j<C;++j){
      go[i][j]=go[go[i][j-1]][j-1];
      sum[i][j]=sum[i][j-1]+sum[go[i][j-1]][j-1]+((LL)go[i][j-1]-go[i][j]<<j-1);
    }
}

LL Query(int tdp,int stp){
  if (l[stp]<=tdp) return (LL)stp-tdp;
  LL res=stp-l[stp];
  int now=1;
  stp=l[stp];
  for (int i=C-1;i>=0;--i)
    if (go[stp][i]>tdp){
      res+=sum[stp][i]+(LL)now*(stp-go[stp][i]);
      stp=go[stp][i];
      now+=1<<i;
    }
  return res+(stp-tdp)*(now+1);
}

Abigail into(){
  scanf("%d",&n);
  for (int i=2;i<=n;++i)
    scanf("%d",&l[i]);
}

Abigail work(){
  l[1]=1;
  Pre_doubly();
}

Abigail getans(){
  scanf("%d",&cq);
  for (int i=1;i<=cq;++i){
    int l,r,x;
    scanf("%d%d%d",&l,&r,&x);
    LL ans=Query(l,x)-Query(r+1,x),len=(LL)r-l+1,g=gcd(ans,len);
    printf("%lld/%lld\n",ans/g,len/g);
  }
}

int main(){
  into();
  work();
  getans();
  return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值