1. 程式人生 > >【LuoguP3994】高速公路

【LuoguP3994】高速公路

題目連結

題目描述

C國擁有一張四通八達的高速公路網樹,其中有n個城市,城市之間由一共n-1條高速公路連線。除了首都1號城市,每個城市都有一家本地的客運公司,可以發車前往全國各地,有若干條高速公路連向其他城市,這是一個樹型結構,1號城市(首都)為根。假設有一個人要從i號城市坐車出發前往j號城市,那麼他要花費Pi*(i城市到j城市的距離)+Qi元。由於距離首都越遠,國家的監管就越鬆,所以距離首都越遠,客運公司的Pi(單位距離價格)越大,形式化的說,如果把高速路網看成一棵以首都為根的有根樹,i號城市是j號城市的某個祖先,那麼一定存在Pi<=Pj。

Sol

假裝我們只用考慮從祖先轉移過來

那麼化下式子就是:

f[i]=min{f[j]P[i]Dis[j]}+Q[i]+P[i]Dis[i]

裡面就是個裸的斜率優化了 然而顯然直接做是不行的,因為這是一顆樹

那麼就需要對斜率優化有更深刻的理解才能做了

一種暴力的做法是用可持久化線段樹來維護單調佇列的陣列

但是主意到我們每次考慮一個元素時只是更改了首尾的指標,並且還修改了至多佇列中的一個元素(被當前元素覆蓋了),那麼我們可以直接暴力撤銷操作,獲得滿分

但其實這樣做複雜度是不對的,因為這樣你會發現一個元素不僅僅是入隊出隊了一次,而是可能被反覆加進來,這樣就會被卡掉

因為斜率優化的實質是維護了一個凸包,也就是說裡面直線的斜率是單調的,那麼我們可以不用暴力出對,而改成二分這個位置然後一次性出隊,這樣複雜度就變成了穩定的O(nlogn)

(然而能直接過有什麼不好的)

暴力出隊:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<cmath>
#include<set>
using namespace std;
inline
int read() { int x=0;char ch=getchar();int t=1; for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-' )t=-1; for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48); return x*t; } const int N=1e6+10; struct edge{ int to,next; }a[N]; int cur[N];int cnt=0; inline void add(int x,int y){a[++cnt]=(edge){y,cur[x]};cur[x]=cnt;} typedef long long ll; int P[N]; int Q[N]; int fa[N],W[N]; int n; int que[N];int h,t; ll dp[N],dis[N]; int head[N],tail[N],pos[N],pre[N]; typedef double db; inline db getk(int p,int q){ return (db)(dp[p]-dp[q])/(db)(dis[p]-dis[q]); } void dfs(int u) { head[u]=h;tail[u]=t; while(h<t&&getk(que[h],que[h+1])<=1.00*P[u]) ++h; dp[u]=dp[que[h]]+(dis[u]-dis[que[h]])*P[u]+Q[u]; while(h<t&&getk(que[t],que[t-1])>getk(u,que[t])) --t; pre[u]=que[++t];que[t]=u;pos[u]=t; for(register int v,i=cur[u];i;i=a[i].next){ v=a[i].to;dis[v]=dis[u]+W[v];dfs(v); } h=head[u];t=tail[u];que[pos[u]]=pre[u]; return; } int main() { n=read(); for(register int i=2;i<=n;++i) fa[i]=read(),W[i]=read(),P[i]=read(),Q[i]=read(),add(fa[i],i); h=1;t=0; dfs(1); for(register int i=2;i<=n;++i) printf("%lld\n",dp[i]); return 0; }