1. 程式人生 > >P3994 高速公路 樹形DP+斜率優化+二分

P3994 高速公路 樹形DP+斜率優化+二分

維護 while 價格 add nod 輸入格式 長度 參數 位置

$ \color{#0066ff}{ 題目描述 }$

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

大寧成為了國家統計局的調查人員,他需要對現在的高速路網進行一次調查,了解從其他每一個城市到達首都1號城市所花費的金錢(路徑必須是簡單路徑)。

因為有非常多轉車(或不轉車)的抵達首都的方法,所以人工計算這個結果是十分復雜的。大寧非常的懶,所以請你編寫一個程序解決它。

\(\color{#0066ff}{輸入格式}\)

第 1 行包含1個非負整數 n,表示城市的個數。

第 2 到 n 行,每行描述一個除首都之外的城市。其中第 i 行包含4 個非負整數 Fi,Si,Pi,Qi,分別表示 i號城市的父親城市,它到父親城市高速公路的長度,以及乘車價格的兩個參數。

\(\color{#0066ff}{輸出格式}\)

輸出包含 n-1 行,每行包含一個整數。

其中第 i 行表示從 i+1號城市 出發,到達首都最少的乘車費用。

\(\color{#0066ff}{輸入樣例}\)

6
1 9 3 0
1 17 1 9
1 1 1 6
4 13 2 15
4 9 2 4

\(\color{#0066ff}{輸出樣例}\)

27
26
7
43
24

\(\color{#0066ff}{數據範圍與提示}\)

對於前40%的數據1<=n<=1000。

對於另外20%的數據 滿足從第i(i≠1)個城市出發的高速公路連向第i-1個城市。

對於所有的數據1<=n<=1000000,0<=Pi,Qi<=2^31-1,保證結果不會大於2^63-1。

\(\color{#0066ff}{題解}\)

不難想到DP,設\(f[i]\)為從i到根的答案,轉移也很好想

\(f[i] =min(f[i],f[j]+(dis[i])-dis[j])*p[i]+q[i])\)

但是這樣轉移是\(O(n^2)\)的,顯然過不了啊

我們考慮斜率優化

如果x比y優秀,即\(f[x]+(dis[i]-dis[x])*p[i]+q[i]<f[y]+(dis[i]-dis[y])*p[i]+q[i]\)

化簡之後是\(p[i]<\frac{f[x]-f[y]}{dis[x]-dis[y]}\)

這樣就能斜率優化了,用單調隊列維護一個下凸包

但是,這是優化的樹形DP啊, 在一棵子樹內的決策會影響到另一棵子樹!

我們考慮每次轉移單調隊列的變化,看能不能搞點什麽

可以發現,每次head僅僅是移動,其中元素是不變的(祖先鏈不變)

tail呢?每次移動後只會修改一個值!

所以我們可以記錄一下每個點的head和tail,還有修改的元素位置和編號,這樣就可以快速回溯

但是這樣每個點會入隊多次,上界依然是\(O(n^2)\)

又因為元素是不變的,所以,我們考慮二分來優化這個過程,每次二分兩次,找到轉移點和修改點即可

#include<bits/stdc++.h>
#define LL long long
LL in() {
    char ch; LL x = 0, f = 1;
    while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
    for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
    return x * f;
}
const int maxn = 1e6 + 100;
LL dep[maxn], f[maxn];
LL p[maxn], q[maxn];
int st[maxn], head, tail;
int n;
struct node {
    int to, dis;
    node *nxt;
    node(int to = 0, int dis = 0, node *nxt = NULL): to(to), dis(dis), nxt(nxt) {}
}*h[maxn];
void add(int from, int to, int dis) { h[from] = new node(to, dis, h[from]); }
void dfs(int x) { for(node *i = h[x]; i; i = i->nxt) dep[i->to] = dep[x] + i->dis, dfs(i->to); }
double K(int x, int y) { return (double)(f[x] - f[y]) / (double)(dep[x] - dep[y]); }
void work(int x) {
    int nowhead = head, nowtail = tail;
    int l = head, r = tail - 1, ans = -1;
    while(l <= r) {
        int mid = (l + r) >> 1;
        if(p[x] <= K(st[mid], st[mid + 1])) ans = mid, r = mid - 1;
        else l = mid + 1;
    }
    if(~ans) head = ans;
    else head = tail;
    f[x] = f[st[head]] + (dep[x] - dep[st[head]]) * p[x] + q[x];
    l = head, r = tail - 1, ans = -1;
    while(l <= r) {
        int mid = (l + r) >> 1;
        if(K(st[mid], st[mid + 1]) < K(st[mid + 1], x)) ans = mid, l = mid + 1;
        else r = mid - 1;
    }
    if(~ans) tail = ans + 1;
    else tail = head;
    int now = st[++tail];
    st[tail] = x;
    for(node *i = h[x]; i; i = i->nxt) work(i->to);
    head = nowhead, st[tail] = now, tail = nowtail;
}
int main() {
    n = in();
    int x, y;
    for(int i = 2; i <= n; i++) {
        x = in(), y = in();
        add(x, i, y);
        p[i] = in(), q[i] = in();
    }
    dfs(1);
    for(node *i = h[1]; i; i = i->nxt) st[head = tail = 1] = 1, work(i->to);
    for(int i = 2; i <= n; i++) printf("%lld\n", f[i]);
    return 0;
}

P3994 高速公路 樹形DP+斜率優化+二分