1. 程式人生 > >LOJ#2249 Luogu P2305「NOI2014」購票

LOJ#2249 Luogu P2305「NOI2014」購票

幾乎肝了半個下午和整個晚上

斜率優化的模型好多啊...

LOJ $2249 Luogu P2305


題意

給定一棵樹,第$ i$個點如果離某個祖先$ x$的距離不超過$ L_i$,可以花費$ P_i·dist(i,x)+Q_i$的代價跳到點$ x$,

求每個點走到根的最小代價

點數不超過$ 2·10^5$


 

$ Solution$

用$dis_x$表示$ x$到根的距離

首先考慮一條鏈的情況

嘗試斜率優化

容易推出兩個點$j,k$,若$ dis_k>dis_j且k比j優$當且僅當$ \frac{dp_k-dp_j}{dis_k-dis_j}<P_i$

由於有$ P_i$不具有單調性可以維護單調棧然後每次在裡面二分

但是由於有$ L_i$這個限制,這個單調棧並不容易直接維護

考慮$ CDQ$分治,即把問題轉化成求左半部分對右半部分的轉移貢獻

將右半部分的點按照$ dis[i]-L_i$從大到小排序

每次列舉右邊的點,將左邊的點按距離從大到小加入單調棧中

顯然現在沒有$ L_i$的限制就可以直接在單調棧中二分

時間複雜度$ O(n \ log^2 \  n)$

 

然後就是把鏈上的問題放到樹上

樹上分治很容易想到直接點分治

流程如下:

1.找到當前子樹的重心

2.將重心及重心外側的子樹遞迴計算(相當於序列上$CDQ$分治中的$calc(L,mid)$)

3.計算重心外側的子樹對重心以下的若干棵子樹的貢獻(相當於序列上$ CDQ$分治中左邊對右邊的貢獻)

4.遞迴計算重心以下的各棵子樹的答案(相當於序列上$ CDQ$分治中的$calc(mid+1,R)$)

總複雜度$ O(n \ log^2 \  n)$


 

$ my \ code$

#include<ctime>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include
<queue> #define M 200010 #define rt register int #define ll long long using namespace std; namespace fast_IO{ const int IN_LEN=10000000,OUT_LEN=10000000; char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1; inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;} inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;} inline void flush(){fwrite(obuf,1,oh-obuf,stdout);} } using namespace fast_IO; #define getchar() getchar_() #define putchar(x) putchar_((x)) inline ll read(){ ll x = 0; char zf = 1; char ch = getchar(); while (ch != '-' && !isdigit(ch)) ch = getchar(); if (ch == '-') zf = -1, ch = getchar(); while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar(); return x * zf; } void write(ll y){if(y<0)putchar('-'),y=-y;if(y>9)write(y/10);putchar(y%10+48);} void writeln(const ll y){write(y);putchar('\n');} int i,j,k,m,n,x,y,z,cnt; ll dis[M],lim[M],f[M],p[M],q[M]; int F[M],L[M],N[M],a[M],fa[M]; void add(int x,int y){ a[++k]=y; if(!F[x])F[x]=k; else N[L[x]]=k; L[x]=k; } int size[200010],nowmin,all,Q[200010],sta[200010],top,Root,troot; bool vis[200010]; inline bool cmp(int x,int y){return dis[x]-lim[x]>dis[y]-lim[y];} inline double slope(int x,int y){return (double)(f[x]-f[y])/(dis[x]-dis[y]);} void get(int x){ size[x]=1;int maxsum=0; for(rt i=F[x];i;i=N[i])if(!vis[a[i]]){ get(a[i]);size[x]+=size[a[i]]; maxsum=max(maxsum,size[a[i]]); } maxsum=max(maxsum,all-size[x]+1); if(maxsum<nowmin)nowmin=maxsum,Root=x; } int len; void dfs(int x){Q[++len]=x;for(rt i=F[x];i;i=N[i])if(!vis[a[i]])dfs(a[i]);} void calc(int x,int sz){ if(sz<=1)return; nowmin=all=sz;get(x);int u=Root; for(rt i=F[u];i;i=N[i])vis[a[i]]=1; calc(x,sz-size[u]+1);len=0; for(rt i=F[u];i;i=N[i])dfs(a[i]); sort(Q+1,Q+len+1,cmp);top=0; for(rt i=1,pl=u;i<=len;i++){ while(dis[pl]>=dis[Q[i]]-lim[Q[i]]&&pl!=fa[x]){ while(top>=2&&slope(pl,sta[top])>slope(sta[top],sta[top-1]))top--; sta[++top]=pl;pl=fa[pl]; } f[Q[i]]=min(f[Q[i]],f[sta[top]]+(dis[Q[i]]-dis[sta[top]])*p[Q[i]]+q[Q[i]]); if(top<=1)continue; int L=1,R=top-1; while(L<=R){ const int mid=L+R>>1; if((f[sta[mid]]-f[sta[mid+1]])<=p[Q[i]]*(dis[sta[mid]]-dis[sta[mid+1]])) R=mid-1;else L=mid+1; } f[Q[i]]=min(f[Q[i]],f[sta[L]]+(dis[Q[i]]-dis[sta[L]])*p[Q[i]]+q[Q[i]]); } for(rt i=F[u];i;i=N[i])calc(a[i],size[a[i]]); } int main(){ n=read();x=read(); memset(f,10,sizeof(f));f[1]=0; for(rt i=2;i<=n;i++){ fa[i]=read();add(fa[i],i); dis[i]=dis[fa[i]]+read(); p[i]=read();q[i]=read();lim[i]=read(); } calc(1,n); for(rt i=2;i<=n;i++)writeln(f[i]); return flush(),0; }