题解 CF932F【Escape Through Leaf】

Link

又是一道妙题。

又是一篇迟到一周的题解。

题意

给一棵树,n 个节点,每个节点又两个权值 aibi,记 xy 表示 xy 的转移,条件为 yx 的子树内,代价为 ax×by

求每个节点到任意叶节点的代价最小值。

2n105,105ai,bi105

思路

首先思考 dp,设 dpi 表示节点 i 的答案,于是转移方程很好搞出来:

dpx=minysubtree(x)(dpy+ax×by)

可以发现,这样 dpO(n2) 的,不过提供了一个很不错的思路。

接下来,我们考虑使用数据结构优化这个 dp

我们不知道为什么拿出一个一次函数 f(x)=b+ky,我们惊奇地发现,如果将 dpy,by 分别看作 b,k,则问题转化为求平面内一些一次函数与直线 x=ax 的交点中纵坐标的最小值。

然后既然要维护平面直角坐标系里面的一次函数最值问题,我们可以使用李超线段树,不会的可以先去做 [HEOI2013]Segment

然后我们发现,如果对于每一次转移重建李超线段树的话,复杂度是 O(n2logn) 的。。。我们发现当前节点的线段树维护的函数就是其子树的线段树维护的函数的并集,考虑线段树合并,直接 dfs 时进行合并,合并完再统计答案即可。合并的方法与板子没什么不同,只是把维护的信息改为最小值即可,不会的可以去做 【模板】线段树合并

还有一点要注意,我们维护的是 x=ax 的线段树,题目中 ai 的数据范围是 [105,105],我们要将其向右平移 105[0,2×105] 的下标范围防止出现负下标,记得算斜率时要减回去。

可以看出时间复杂度是 O(nlogn) 的。

至此解决了这道题。

代码(也是很短的):

#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace IO{//by cyffff

}
const int N=1e5+10,rsh=1e5;
const ll INF=2e18+10;
int n,cnt,A[N],B[N],head[N];
ll dp[N];
struct Line{
    ll b,k;
    //y=b+kx
    Line(ll x=0,ll y=0){ b=x,k=y; }
    inline ll f(int x){
        x-=rsh;
        return k*x+b;
    }
}line[N];
struct Edge{
    int from,to,nxt;
}edgea[N<<1];
inline void add(int u,int v){
    cnt++;
    edgea[cnt].nxt=head[u];
    edgea[cnt].to=v;
    head[u]=cnt;
}
struct Li_Chao_segment{
    int cnt=0,root[N];
    struct node{
        int l,r;
        int best;
    }a[N];
    #define ls (a[rt].l)
    #define rs (a[rt].r)
    inline void insert(int &rt,int pre,int l=0,int r=rsh*2){
        if(!rt) return (void)(a[rt=pre]=(node){ 0,0,a[pre].best });
        int mid=l+r>>1;
        int &q=a[rt].best;
        int &x=a[pre].best;
        double l1=line[q].f(l),r1=line[q].f(r);
        double l2=line[x].f(l),r2=line[x].f(r);
        if(l1<=l2&&r1<=r2) return;
        if(l1>=l2&&r1>=r2) return (void)(a[rt].best=x);
        if(line[q].k>line[x].k){
            if(line[q].f(mid)>line[x].f(mid)){
                swap(q,x);
                insert(ls,pre,l,mid);
            }else{
                insert(rs,pre,mid+1,r);
            }
        }else{
            if(line[q].f(mid)<=line[x].f(mid)){
                insert(ls,pre,l,mid);
            }else{
                swap(q,x);
                insert(rs,pre,mid+1,r);
            }
        }
    }
    inline ll query(int rt,int k,int l=0,int r=rsh*2){
        if(!rt) return INF;
        ll ans=line[a[rt].best].f(k);
        if(l==r) return ans;
        int mid=l+r>>1;
        if(k<=mid){
            ans=min(ans,query(ls,k,l,mid));
        }else{
            ans=min(ans,query(rs,k,mid+1,r));
        }
        return ans;
    }
    inline int merge(int x,int y,int l=0,int r=rsh*2){
        if(!x||!y) return x+y;
        if(l==r)
            return line[a[x].best].f(l)>line[a[y].best].f(l)?y:x;
        int mid=l+r>>1;
        a[x].l=merge(a[x].l,a[y].l,l,mid);
        a[x].r=merge(a[x].r,a[y].r,mid+1,r);
        insert(x,y,l,r);
        return x;
    }
    inline void dfs(int rt,int f){
        for(int i=head[rt];i;i=edgea[i].nxt){
            if(edgea[i].to!=f){
                dfs(edgea[i].to,rt);
                root[rt]=merge(root[rt],root[edgea[i].to]);
            }
        }
        if(root[rt]) dp[rt]=query(root[rt],A[rt]);
        line[rt]=Line(dp[rt],B[rt]);
        a[++cnt]=(node){ 0,0,rt };
        insert(root[rt],cnt);
    }
}t;
int main(){
    n=read();
    for(int i=1;i<=n;i++){
        A[i]=read()+rsh;
    }
    for(int i=1;i<=n;i++){
        B[i]=read();
    }
    for(int i=1;i<n;i++){
        int u=read(),v=read();
        add(u,v);
        add(v,u);
    }
    t.dfs(1,0);
    for(int i=1;i<=n;i++){
        write(dp[i]);
        putc(' ');
    }
    flush();
}

再见 qwq~

posted @   ffffyc  阅读(7)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示