题解[CF932F] Escape Through Leaf

时间:2021-07-21
本文章向大家介绍题解[CF932F] Escape Through Leaf,主要包括题解[CF932F] Escape Through Leaf使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

题目描述

一棵有n个节点的树,每个节点有两个权值 \(a\)i\(b\)i\(i\)点只能走到\(i\)子树内的点,从\(i\)\(j\)的花费为 \(a\)i\(b\)j 的乘积
求从根到叶子节点的最短路径长度(其中1为根)
\(n\) \(\leq\) 100000, |\(a\)i|, |\(b\)i| \(\leq\) 100000

分析

首先来一个正常人都不会这样想的暴力:
我们暴力枚举每一个点\(i\),然后暴力枚举\(i\)子树内的点,再暴力算出从\(i\)到子树内每个点的花费,然后暴力将其连边,接着暴力地将每个叶子节点向一个辅助点连一条权值为0的边,最后暴力地以1为起点,那个辅助点为终点,跑暴力的SPFA算法(因为有负边), 复杂度也是十分暴力的\(O(n\)\(3\)\()\)
当然如果他不卡SPFA(相信也没人会卡)的话,复杂度可以达到十分优秀的\(O(kn\)\(2\)\()\)
当然如果你发现了这其实是一个DAG,复杂度其实可以达到\(O(n\)\(2\)\()\)

好的胡扯到此结束,我们现在来考虑正解
首先正常人一眼过去肯定会发现这题可以DP,我们设\(f\)\(i\)为根节点1到i节点的最小花费
那么我们有

\[f_i = min_{j,j是i的后代} \qquad {f_j + a_i * b_j} \]

如果我们暴力按照上面的转移方程来做的话,你会发现他就是上面提到的DAG最短路(DAG最短路的实质其实就是DP)
我们再观察一下这个柿子

不难发现,其中\(a\)\(i\)只和i有关,\(b\)\(j\)\(f\)\(j\)只和j有关
那么\(f_i\)其实就是求多条直线\(y = b_j * x + f_j\)\(x = a_i\)处的最大值

那这就是很经典的李超线段树问题了
但由于是树形DP,故此题还需要合并李超线段树(其实就是普通的线段树合并啦

代码

#include<bits/stdc++.h>
#define int LL
using namespace std;
typedef long long LL;
 
const int N = 200010;
const int inf = 1e17;
 
struct line{
    double k, b;
}p[N << 2];
int tree[N << 5], ls[N << 5], rs[N << 5], n, tot,
    head[N], nxt[N], to[N], a[N], b[N], root[N], cnt;
long long dp[N];
 
int calc(int id, int d) { return p[id].k * d + p[id].b; }
 
inline int read(){
    int x = 0, f = 1;
    char c = getchar();
    while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar(); }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
 
void ins(int l, int r, int &x, int u)
{
    if(!x) { x = ++tot; tree[x] = u; return ; }
    int v = tree[x], mid = (l + r) / 2,
        resu = calc(u, mid), resv = calc(v, mid);
    if(l == r) { if(resu < resv) tree[x] = u; return ; }
    if(!v) { tree[x] = u; return ; }
    if(p[u].k > p[v].k){
        if(resu < resv){
            tree[x] = u;
            ins(mid + 1, r, rs[x], v);
        }
        else ins(l, mid, ls[x], u);
    }
     
    else if(p[u].k < p[v].k){
        if(resu < resv){
            tree[x] = u;
            ins(l, mid, ls[x], v);
        }
        else ins(mid + 1, r, rs[x], u);
    }
     
    else if(resu < resv) tree[x] = u;
}
 
LL que(int l, int r, int x, int d)
{
    if(l > d || r < d || !x) return inf;
    if(l == r) return calc(tree[x], d);
    int mid = (l + r) / 2;
    return min(1ll * calc(tree[x], d), min(que(l, mid, ls[x], d), que(mid + 1, r, rs[x], d)));
}
 
int merge(int x, int y, int l, int r)
{
    if(!x || !y) return x + y;
    int tx = tree[x], ty = tree[y], mid = (l + r) / 2;
    if(l == r) return calc(tx, mid) < calc(ty, mid) ? x : y;
    ls[x] = merge(ls[x], ls[y], l, mid);
    rs[x] = merge(rs[x], rs[y], mid + 1, r);
    ins(l, r, x, tree[y]);
    return x;
}
 
void addedge(int u, int v)
{
    to[++cnt] = v;
    nxt[cnt] = head[u], head[u] = cnt;
}
 
void dfs(int u, int f)
{
    bool flag = false;
    for(int i = head[u]; i; i = nxt[i]){
        int v = to[i];
        if(v == f) continue;
        flag = true;
        dfs(v, u);
        root[u] = merge(root[u], root[v], -1e5, 1e5);
    }
    dp[u] = que(-1e5, 1e5, root[u], a[u]);
    if(!flag) dp[u] = 0;
    p[u].k = b[u], p[u].b = dp[u];
    ins(-1e5, 1e5, root[u], u);
}
 
signed main()
{
    n = read();
    for(int i = 1; i <= n; i++) a[i] = read();
    for(int i = 1; i <= n; i++) b[i] = read();
     
    int u, v;
    for(int i = 1; i < n; i++){
        u = read(), v = read();
        addedge(u, v);
        addedge(v, u);
    }
     
    dfs(1, 0);
    for(int i = 1; i <= n; i++) printf("%lld ", dp[i]);
    return 0;
}

需要注意的是,如果线性DP出现\(f_i = min(b_j * a_i + c_j)\)的转移方程,我们应该首先考虑斜率优化(即\(a_i\)是否单调)

原文地址:https://www.cnblogs.com/sgweo8yspace/p/14992375.html