【Codeforces 675E】Trains and Statistic
贪心?
题目大意:
有 $1\sim n$ 这 $n$ 个位置,对于 $i\in[1,n-1]$,有 $a_i\in[i+1,n]$,表示 $i$ 可以花费 $1$ 的代价走到 $[i+1,a_i]$ 的任意一个位置。
令 $d_{i,j}$ 表示 $i$ 到 $j$ 的最小花费,求 $\sum\limits_{i=1}^{n-1}\sum\limits_{j=i+1}^{n} d_{i,j}$ 的值。
题解:
按照起点从后往前依次考虑。
对于每个位置出发,我们要使其每一步能走的范围尽可能大。
那么,我们如果能走到 $[l,r]$,为了下一步能走得更远,我们需要在 $[l,r]$ 中选取一个 $a_i$ 最大的 $i$。
这个就是个区间 RMQ 问题。不难在 $O(n\log n)$ 的复杂度内解决。
那么怎么计算贡献呢?
我们考虑对位置 $i$,其第一次走的最大位置为 $k$,从 $k$ 的信息直接转移到 $i$ 的信息。
令 $f_i=\sum\limits_{j=i}^n d_{i,j}$.
考虑 $f_i$ 与 $f_k$ 的关系。如图:
其中,A 段是 $f_i$ 比 $f_k$ 多走的部分,C 段是 $i$ 直接走无法走到,需要走到 $k$ 再往后继续走的。所以 A 段和 C 段的每个数都要多计算 $1$ 的贡献。
上式的意思就是分别加上了 A 段和 C 段的贡献。
最后的答案就是所有 $f_i$ 之和。
时间复杂度就是 RMQ 部分的 $O(n\log n)$。这里的 RMQ 不需要使用 ST 表、线段树等算法,可以使用单调栈维护,查询直接二分即可。
Code:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int N=1e5+6;
typedef long long LL;
int n,a[N];
LL ans,f[N];
vector<int>vec;
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<n;++i)
cin>>a[i];
f[n-1]=ans=1;
vec.push_back(n-1);
for(int i=n-2;i;--i){
int k=*(upper_bound(vec.rbegin(),vec.rend(),a[i])-1);
f[i]=f[k]+(k-i)+(n-a[i]);
ans+=f[i];
while(!vec.empty()&&a[i]>=a[vec.back()])vec.pop_back();
vec.push_back(i);
}
cout<<ans<<endl;
return 0;
}