题目描述
有 n 个星球,它们的编号是 1 到 n,它们坐落在同一个星系内,这个星系可以抽象为一条数轴,每个星球都是数轴上的一个点,特别地,编号为 i 的星球的坐标是 i。
一开始,由于科技上的原因,这 n 个星球的居民之间无法进行交流,因此他们也不知道彼此的存在。现在,这些星球独立发展出了星际穿越与星际交流的工具。对于第 i 个星球,他通过发射强力信号,成功地与编号在 的所有星球取得了联系(编号为 1 的星球没有发出任何信号),取得联系的两个星球会建立 **双向** 的传送门,对于建立了传送门的两个星球 u,v,u 上的居民可以花费 1 单位时间传送到 v,v 上的居民也可以花费 1 单位时间传送到 u ,我们用 dist(x,y) 表示从编号为 x 的星球出发,通过一系列星球间的传送门,传送到编号为 y 的星球最少需要花费的时间。
现在有 q 个星际商人,第 i 个商人初始所在的位置是 x_i, 他的目的地是 中的其中一个星球,保证
。他会在这些星球中等概率挑选一个星球 y (每个星球都有一样的概率被选中作为目的地),然后通过一系列星球的传送门,**花费最少的时间**到达星球 y 。商人想知道他花费的期望时间是多少?也就是计算
。
对于 的数据,满足
思路
接下来让我总结下解题思路,这道题运用了倍增的思路,对我来说很有挑战,看了题解过了题之后我来总结一下。
首先题目给出2到n的范围a[i]代表第i个点一步可以跳到左侧最远的点。题目给出q个查询,每个查询包含一个区间[l,r]和一个点x,问对于一个点x,等可能到达区间任意值的期望值,实际上就是x到区间内所有值总和除以区间长度。
首先从求解角度,题目求的就是[l,x]的步数总和-[r+1,x]的步数总和。首先考虑怎么维护每个点到前面的步数和,根据题目给定的a数组,可以想到先求对于每个点i求出i步能到达的最远的点,线性必超时,考虑倍增。
设dp[i][j]为i位置向前跳2^j步最远的点,很容易看出每个点向前跳1步就是题目给定的a[i],所以有dp[i][0]=a[i](i>1),由于i位置向前面的跳跃步数可能比i后面位置向前跳的步数小,导致不是最优(如i的a[i]大于a[i+1]会导致i跳到小于i的位置会花费更多步数),所以要从最后面n开始递推,所以有dp[n][0]=a[n],dp[i][j]=dp[dp[i][j-1][j-1]。
随后可以由类似求前缀和的方法递推出sum数组,sum[i][j]代表i位置向前跳2^j步的区间步数和,sum[n][0]=1*(n-a[n])(此时步数为1),sum[i][j]=sum[i][j-1]+sum[dp[i][j-1]][j-1]+times*(dp[i][j-1]-dp[i][j]),代表位置i跳2^j的步数和由跳2^(j-1)的区间和加上i跳2^(j-1)步到达的最远位置跳2^(j-1)步的区间和,最后因为对于i位置后者的步数要加上区间差(dp[i][j-1]-dp[i][j])乘上当前步数(1<<(j-1)),到此为止dp数组和sum数组维护完成。
然后进行查询x位置到l位置的区间和,也是简单的倍增处理,ans代表总和,times代表已经跳了几次,x代表当前位置,从最大的指数递减枚举,如果dp[x][i]大于l代表还没走完,就可以更新答案,ans+=times*(x-dp[x][i])+sum[x][i],因为sum[x][i]代表x位置跳2^i次的区间和,times*(x-dp[x][i])则是额外加上sum的偏移,例如假如已经走了一步,不能直接加上sum[x][i],因为sum数组计算是从第一步开始,这时要把当前位置到最前的位置这段区间,也就是sum覆盖的区间,把这部分区间的和乘上步数,才能正确处理总的区间和。最后gcd计算进行通分化到最简输出答案,完毕
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 500;
#define endl '\n'
#define ll long long
const int MOD=1e9+7;
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
ll n;cin>>n;
vector<ll> a(n+1);
for(int i=2;i<=n;i++)
cin>>a[i];
vector<vector<ll>> dp(n+1,vector<ll>(30));//代表从i开始跳2^j最小的位置
vector<vector<ll>> sum(n+1,vector<ll>(30));//代表从i开始跳2^j最小的位置的和
dp[n][0]=a[n],sum[n][0]=n-a[n];
for(int i=n-1;i>=2;i--)//由于i位置向前面的跳跃步数可能比i后面位置向前跳的步数小,要从后推
{
dp[i][0]=min(dp[i+1][0],a[i]);
sum[i][0]=i-dp[i][0];
}
for(int j=1;j<=29;j++)
{
for(int i=(1<<j);i<=n;i++)
{
dp[i][j]=dp[dp[i][j-1]][j-1];
sum[i][j]=sum[i][j-1]+sum[dp[i][j-1]][j-1]+(1<<(j-1))*(dp[i][j-1]-dp[i][j]);
//加上区间差(dp[i][j-1]-dp[i][j])乘上当前步数(1<<(j-1))
}
}
auto query=[&](ll x,ll l)//x到l的最小次数
{
if(l>=a[x])return x-l;
ll times=1,ans=x-a[x];//先跳一次
x=a[x];
for(int i=29;i>=0;i--)
{
if(dp[x][i]>=l)
{
ans+=times*(x-dp[x][i])+sum[x][i];
//因为sum[x][i]代表x位置跳2^i次的区间和,times*(x-dp[x][i])则是额外加上sum的偏移
times+=(1<<i);
x=dp[x][i];
}
}
if(x>l)ans+=(times+1)*(x-l);
return ans;
};
ll q;cin>>q;
while(q--)
{
ll l,r,x;cin>>l>>r>>x;
ll a=query(x,l)-query(x,r+1);
ll b=r-l+1;
ll g=__gcd(a,r-l+1);
a/=g,b/=g;
cout<<a<<'/'<<b<<endl;
}
return 0;
}

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



