[NOI2014] 购票
设f[x]为从x跑到1的最小花费,基本转移如下
\[\left\{
\begin{aligned}
f[1]&=0\\
f[x]&=\min_{dep[x]-dep[y]\le l[x]} f[y]+p[x](dep[x]-dep[y])+q[x]\\
&=p[x]dep[x]+q[x]+\min_{dep[x]-dep[y]\le l[x]} f[y]-p[x]dep[y]
\end{aligned}
\right.
\]
有斜率优化的影子
\[f[x]=p[x]dep[x]+q[x]+(f[y]-p[x]dep[y])\\
f[y]=\underline{p[x]}dep[y]+\underline{f[x]-(p[x]dep[x]+q[x])}
\]
于是在dp的过程中需要维护根到x父亲的一条链,二分求出被x接受的链的后缀,需要得到后缀上点(dep,f)的凸包,然后二分斜率求答案 。压入点、弹出点、计算后缀的凸包……显然这时间爆炸。
注意到这个后缀是可以分成若干段分别计算的,考虑对栈优雅地分块?时间复杂度\(O(n\sqrt n\log n)\)不太友好。
可以使用带撤销的二进制分组来肝,即用线段树来做一些完整区间(为方便处理,提前把树处理成满二叉树)的凸包,除了每层的最后一的满区间。
(其实这根树链剖分的做法已经很相似了啊)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
const int M=N*8;
int n;
int head[N],to[N],lst[N];
int fa[N][20],dep[N],mxd;
ll wit[N],dis[N],P[N],Q[N],L[N],f[N];
int len=1,id[N*2];
namespace SGT {
int bel[M],num[M],siz[M],cnt[25],tmp[N];
vector<int> tre[M];
bool hav[M];
#define ls (x<<1)
#define rs (x<<1|1)
void build(int x,int l,int r) {
num[x]=++tmp[bel[x]=id[r-l+1]];
if(l==r) {tre[x].resize(1); return;}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
}
void upd(int x,int l,int r,int p,int k) {
if(++siz[x]==r-l+1) ++cnt[bel[x]];
if(l==r) {
tre[x][0]=k;
hav[x]=true;
return;
}
int mid=(l+r)>>1;
if(p<=mid) upd(ls,l,mid,p,k);
else upd(rs,mid+1,r,p,k);
}
void del(int x,int l,int r,int p) {
hav[x]=false;
if(--siz[x]==r-l) --cnt[bel[x]];
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) del(ls,l,mid,p);
else del(rs,mid+1,r,p);
}
#define X(i) dis[i]
#define Y(i) f[i]
inline ll calc(int y,int x) {return f[y]+P[x]*(dis[x]-dis[y])+Q[x];}
inline bool cross(ll x1,ll y1,ll x2,ll y2) {return 1.0*x1*y2>=1.0*x2*y1;}
inline void merge(vector<int> &a,vector<int> &l,vector<int> &r) {
int top=0;
for(auto i:l) tmp[++top]=i;
for(auto i:r) {
while(top>1&&cross(X(i)-X(tmp[top-1]),Y(i)-Y(tmp[top-1]), // 凸壳部分
X(tmp[top])-X(tmp[top-1]),Y(tmp[top])-Y(tmp[top-1])))
top--;
tmp[++top]=i;
}
a.resize(top);
for(int i=1; i<=top; ++i) a[i-1]=tmp[i];
}
void work(int x,int l,int r) {
if(hav[x]) return;
hav[x]=true;
int mid=(l+r)>>1;
work(ls,l,mid);
work(rs,mid+1,r);
merge(tre[x],tre[ls],tre[rs]);
}
inline ll calc(vector<int>& a,int x) {
int l=0,r=a.size()-2,mid;
ll ans=calc(a.back(),x),K=P[x];
while(l<=r) {
mid=(l+r)>>1;
ans=min(ans,calc(a[mid],x));
if(cross(1,K,X(a[mid+1])-X(a[mid]),Y(a[mid+1])-Y(a[mid]))) r=mid-1;
else l=mid+1;
}
return ans;
}
ll query(int x,int l,int r,int L,int R,int X) {
if(L<=l&&r<=R) {
if(!hav[x]&&num[x]<cnt[bel[x]]) work(x,l,r);
if(hav[x]) return calc(tre[x],X);
}
int mid=(l+r)>>1; ll ans=1e18;
if(L<=mid) ans=query(ls,l,mid,L,R,X);
if(mid<R) ans=min(ans,query(rs,mid+1,r,L,R,X));
return ans;
}
}
inline void add_edge(int x,int y,int w) {
static int cnt=0;
to[++cnt]=y;
wit[cnt]=w;
lst[cnt]=head[x];
head[x]=cnt;
}
inline ll calc(int x) {
int z=fa[x][0],y=x;
for(int i=19; ~i; --i)
if(fa[y][i]&&dis[x]-dis[fa[y][i]]<=L[x]) y=fa[y][i];
return SGT::query(1,1,len,dep[y],dep[z],x);
}
void pre(int x) {
mxd=max(mxd,dep[x]=dep[fa[x][0]]+1);
for(int i=1; (1<<i)<=dep[x]; ++i) fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=head[x]; i; i=lst[i]) {
dis[to[i]]=dis[x]+wit[i];
pre(to[i]);
}
}
void dfs(int x) {
if(x!=1) f[x]=calc(x);
SGT::upd(1,1,len,dep[x],x);
for(int i=head[x]; i; i=lst[i]) dfs(to[i]);
SGT::del(1,1,len,dep[x]);
}
int main() {
ll val;
scanf("%d%lld",&n,&val);
for(int i=2; i<=n; ++i) {
scanf("%d%lld%lld%lld%lld",&fa[i][0],&val,P+i,Q+i,L+i);
add_edge(fa[i][0],i,val);
}
pre(1);
for(int i=1; len<mxd; len<<=1,++i) id[len]=i;
SGT::build(1,1,len);
dfs(1);
for(int i=2; i<=n; ++i) printf("%lld\n",f[i]);
return 0;
}
代码大规模借鉴@i207M。