1. 程式人生 > >cogs2652 秘術「天文密葬法」

cogs2652 秘術「天文密葬法」

splay 題目 bit 復雜 one 廣告 turn lap 等於

傳送門:http://cogs.pro/cogs/problem/problem.php?pid=2652

【題解】

學習了一發長鏈剖分,感覺十分茲磁

廣告:長鏈剖分 - fjzzq2002

那麽我再說一遍吧,以本題為例。

題目大意:給一棵樹,每個點有點權$A_i$和$B_i$,找一條長度為$m$的路徑,使得$\frac{\sum_{j~in~route}A_j}{\sum_{j~in~route}B_j}$最小。

顯然分數規劃,二分答案$p$,那麽將每個點的點權變成$A_j - p*B_j$,顯然最後只要判是否存在一條長度為$m$的路徑使得總和小於等於0即可。

首先點分治是可行的,總復雜度為$O(nlog^2n)$。

考慮一種復雜度為$O(nlogn)$的做法:長鏈剖分。

本文剩下部分不介紹復雜度,要分析復雜度可以看上面鏈接中的口胡,或者Google下

考慮一種dp方法:$f_{x,i}$表示$x$節點的子樹中,往下走$i$步的最小值。

很明顯可以使用子樹合並的技巧來算答案、更新f數組。

但是這樣太慢了,復雜度為$O(n^2)$,還不如寫點分治!

但是,考慮一開始f數組的初始值為最長鏈所在兒子的值,接著依次暴力合並其他兒子,這樣復雜度就對了!

我們還需要考慮的是:

①空間問題

②點權問題

先來考慮空間,我們給按類似輕重鏈剖分(這裏用最長鏈替換重兒子)的方式,給點編號$pos_x$,我們只開一個$O(n)$的dp數組$dp$來完成操作。

那麽令$f_{x,0} = dp_{pos_x}$。有人問,其他的值呢?

考慮$x$往下的最長鏈的值,一定存儲在$dp_{pos_x+1}$開始的連續位置,那麽他們對於$x$,恰好是需要多走一步,那麽實際上他們現在的位置就是對的了,就可以當做是$f_{x,1}$……的了!!!

這樣就非常妙了,我們用$O(n)$的空間,實現了$O(n^2)$的動態規劃。

考慮加入一個點還需要點權,那麽如果暴力把長鏈上的所有dp值肯定不現實,復雜度肯定是錯的。

考慮每個點引入$tag_x$,表示這個點全局加了多少,類似於noip2016 蚯蚓的全局增加量。只不過這裏的全局指的是子樹。

然後能做啦!

代碼還很好寫,比垃圾

點分治好多啦!

技術分享
# include <stdio.h>
# include <string.h>
# include <iostream>
# include <algorithm>
// # include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int N = 3e4 + 10, M = 6e4 + 10;
const int mod = 1e9+7;

int n, m, A[N * 7], B[N * 7], head[N], nxt[M], to[M], tot = 0; 
inline void add(int u, int v) {
    ++tot; nxt[tot] = head[u]; head[u] = tot; to[tot] = v;
}
inline void adde(int u, int v) {
    add(u, v), add(v, u);
}

int dep[N], mxd[N], son[N], sz[N]; 
inline void pre_dfs(int x, int fa = 0) {
    dep[x] = dep[fa] + 1;
    mxd[x] = dep[x]; son[x] = 0;
    for (int i=head[x]; i; i=nxt[i]) {
        if(to[i] == fa) continue;
        pre_dfs(to[i], x);
        if(mxd[to[i]] > mxd[x]) mxd[x] = mxd[to[i]], son[x] = to[i];        
    }
    sz[x] = mxd[x] - dep[x]; 
}

int pos[N], idx; 
inline void pre_pos(int x, int fa = 0) {
    pos[x] = ++idx;
    if(son[x]) pre_pos(son[x], x);
    for (int i=head[x]; i; i=nxt[i]) 
        if(to[i] != fa && to[i] != son[x]) pre_pos(to[i], x);        
}

double mid_check, ans;
double dp[N], tag[N]; 
inline void solve(int x, int fa = 0) {
    double *f = &dp[pos[x]], C = (double)A[x] - mid_check * B[x]; 
    if(son[x] == 0) {    //leaf
        f[0] = C; tag[x] = 0; 
        if(m == 0) ans = min(ans, f[0]); 
        return ;
    }
    solve(son[x], x); f[0] = -tag[son[x]];
    tag[x] = tag[son[x]] + C; 
    for (int i=head[x], y; i; i=nxt[i]) {
        if(to[i] == fa || to[i] == son[x]) continue;
        solve(y = to[i], x);
        double *g = &dp[pos[y]];
        for (int j=0; j<=sz[y] && j<m; ++j)
            if(m-1-j <= sz[x]) ans = min(ans, f[m-1-j] + tag[x] + g[j] + tag[y]); 
         for (int j=0; j<=sz[y]; ++j) f[j+1] = min(f[j+1], g[j] + tag[y] + C - tag[x]); 
    }
    if(m <= sz[x]) ans = min(ans, f[m] + tag[x]); 
}


inline bool chk(double x) {
     ans = 1e18; mid_check = x;
     solve(1); 
    return ans <= 0; 
}

int main() {
    freopen("cdcq_b.in", "r", stdin);
    freopen("cdcq_b.out", "w", stdout); 
    cin >> n >> m; 
    for (int i=1; i<=n; ++i) scanf("%d", A+i);
    for (int i=1; i<=n; ++i) scanf("%d", B+i);
    if(m == -1) {
        double ans = 1e18;
        for (int i=1; i<=n; ++i) ans = min(ans, (double)A[i]/B[i]);
        printf("%.2lf\n", ans);
        return 0;
    }
    for (int i=1, u, v; i<n; ++i) {
        scanf("%d%d", &u, &v);
        adde(u, v);
    }
    --m;
    pre_dfs(1); 
    pre_pos(1); 
    double l = 0, r = 1e11, mid;
    while(r-l > 1e-4) {
        mid = (l+r)/2.0; 
        if(chk(mid)) r = mid;
        else l = mid;
    }
    if(l > 5e10) puts("-1");
    else printf("%.2lf\n", l); 
    return 0;
}
View Code

cogs2652 秘術「天文密葬法」