描述:
题意:
给出n个车站,并告诉你第i个车站有从i+1到a[i]个车站的直达票,问你所有车站到其余各个车站的最小车票花费和。
思路:
我们用dp[i]表示第i个车站从i+1到n最小车票花费总和。那么dp[n]=0,于是就可以从n到1逆推,求和计算出答案。
我们再来看如何得到dp[i]。对于从i+1到a[i]的车站,从第i号车站各只需要一张车票即可。但是对于大于a[i]的车站,我们可以找到从i+1到a[i]的a值最大的车站m。因为a[m]最大,所以能到达的范围也就更大,所以从这个m车站到剩下的车站花费车票一定最少的。这也就是我们贪心的依据。
找这个m的时候可以先用线段树求出区间里最大的a值然后用set二分查询一下m
这时候我们发现从m+1到a[i]在dp[m]中已经计算过了,各多算了一次,所以结果要减去a[i]-m,
算得dp[i]=dp[m]+(n-i)-(a[i]-m),
再将所有的dp[i]加起来就好了
线段树专题参考Here
代码:
#include <bits/stdc++.h>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
#define pr(x) cout << #x << "= " << x << " " ;
#define pl(x) cout << #x << "= " << x << endl;
#define ll __int64
using namespace std;
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define For(i,j,n) for(int i=j;i<=n;i++)
template<class T> void read(T&num) {
char CH; bool F=false;
for(CH=getchar();CH<'0'||CH>'9';F= CH=='-',CH=getchar());
for(num=0;CH>='0'&&CH<='9';num=num*10+CH-'0',CH=getchar());
F && (num=-num);
}
int stk[70], tp;
template<class T> inline void print(T p) {
if(!p) { puts("0"); return; }
while(p) stk[++ tp] = p%10, p/=10;
while(tp) putchar(stk[tp--] + '0');
putchar('\n');
}
const int N=1e5+10;
ll dp[N],ans=0;
int Max[N<<2],a[N],n;
set<int>s[N];
void PushUp(int rt){
Max[rt]=max(Max[rt<<1],Max[rt<<1|1]);
}
void build(int l,int r,int rt){//线段树求区间最大值
if(l==r){
Max[rt]=a[l];
return;
}
int m=(r+l)>>1;
build(lson);
build(rson);
PushUp(rt);
}
int query(int L,int R, int l,int r,int rt){
if(L<=l && r<=R)return Max[rt];
int m=(l+r)>>1;
int ret=0;
if(L<=m)ret=max(ret, query(L, R, lson));
if(R>m)ret=max(ret,query(L, R, rson));
return ret;
}
int main(){
read(n);
s[0].insert(n);
for(int i=1; i<n; i++){
read(a[i]);
s[a[i]].insert(i);
}
build(1, n, 1);
dp[n]=0;
for(int i=n-1; i>=1; i--){
int m=query(i+1, a[i], 1, n, 1);//求i+1——a[i]车站之间某车站m使得a[m]最大
m=*s[m].lower_bound(i+1);//返回大于等于i+1中第一个出现a[m]的下标
dp[i]=dp[m]+n-i-(a[i]-m);
ans+=dp[i];
}
print(ans);
return 0;
}