牛客第一场多校H Longest Path &&斜率dp

本文深入解析树形动态规划结合斜率优化算法,针对树中每条路径的长度计算,采用边权差平方和作为度量标准。通过两次斜率DP,分别维护上凸壳和下凸壳,实现高效求解。首次DP处理上凸壳,二次DP处理下凸壳,巧妙利用队列和栈结构应对不同情况下的最大最小值计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给你一颗树,每条边有边权,定义每条路径长度为路径上相邻边 权值差的平方和。

树dp加斜率优化

#include <bits/stdc++.h>
using namespace std;
#define N 200005
#define ll long long
#define go(i,a,b) for(int i=(a);i<=(b);i++)
#define dep(i,a,b) for(int i=(a);i>=(b);i--)
struct no{
    int to,n,w;
};no eg[N*2];
int h[N],tot=1,n;
void add(int u,int to,int w){
    eg[++tot]={to,h[u],w};h[u]=tot++;
    eg[++tot]={u,h[to],w};h[to]=tot++;
}
ll down[N],dis[N],up[N],dp[N];
void dfsdown(int u,int fa){
    for(int i=h[u];i;i=eg[i].n){
        int to=eg[i].to;
        if(to==fa)continue;
        dis[to]=eg[i].w;
        dfsdown(to,u);
        dp[u]=max(dp[u],down[to]);
        down[u]=max(down[u],down[to]+(dis[u]-dis[to])*(dis[u]-dis[to]));
    }
}
ll x(int u){return dis[u]; }
ll y(int u){return down[u]+dis[u]*dis[u]; }
ll dx(int a,int b){return x(a)-x(b); }
ll dy(int a,int b){return y(a)-y(b); }
bool cmp(int a,int b){return dis[a]<dis[b]; }
//up[to]+c[to]^2 = 2*c[u] *c[to]  + up[u]-c[u]^2;
int qu[N],q[N];
void cal(int u,int fa){
    int n=0;
    for(int i=h[u];i;i=eg[i].n){
        int to=eg[i].to;
        if(to==fa)continue;
        q[++n]=to;
        if(fa)up[to]=up[u]+(dis[u]-dis[to])*(dis[u]-dis[to]);
    }
    sort(q+1,q+n+1,cmp);
    int l=0,r=0;
    go(i,1,n){
        int u=q[i];
        while(l<r-1&&dy(qu[r],qu[r-1])<2*dis[u]*dx(qu[r],qu[r-1]))r--;
        if(l<r)up[u]=max(up[u],down[qu[r]]+(dis[u]-dis[qu[r]])*(dis[u]-dis[qu[r]]));
        while(l<r-1&&dy(qu[r],qu[r-1])*dx(u,qu[r])<dy(u,qu[r])*dx(qu[r],qu[r-1]))r--;
        qu[++r]=u;
    }
    l=0,r=0;
    dep(i,n,1){
        int u=q[i];
        while(l<r-1&&dy(qu[r],qu[r-1])<2*dis[u]*dx(qu[r],qu[r-1]))r--;
        if(l<r)up[u]=max(up[u],down[qu[r]]+(dis[u]-dis[qu[r]])*(dis[u]-dis[qu[r]]));
        while(l<r-1&&dy(qu[r],qu[r-1])*dx(u,qu[r])>dy(u,qu[r])*dx(qu[r],qu[r-1]))r--;
        qu[++r]=u;
    }
    dp[u]=max(dp[u],up[u]);
}
  
void dfsup(int u,int fa){
    cal(u,fa);
    for(int i=h[u];i;i=eg[i].n){
        int to=eg[i].to;
        if(to==fa)continue;
        dfsup(to,u);
    }
}
  
void init(){ go(i,1,n)dp[i]=up[i]=down[i]=h[i]=0;tot=1; }
int u,to,w;
int main()
{
    while(cin>>n){
        init();
        go(i,2,n)scanf("%d%d%d",&u,&to,&w),add(u,to,w);
        dfsdown(1,0);
        dfsup(1,0);
        go(i,1,n)printf("%lld\n",dp[i]);
    }
    return 0;
}

对每个结点的子节点排序之后,正反两边斜率dp。

网上对这里的讲解都不是很详细

第一遍斜率dp的时候,维护的是上凸壳,

第二遍斜率dp的时候,维护的是下凸壳。

一开始没弄懂为什么要这样维护,后来发现是和x的递增递减,k的递增递减,取最大最小值有关的。

当x,k都单调时,

当x递增,k递增时,

取最大值,用栈维护上凸壳, 取最小值,用队列维护下凸壳

当x递增,k递减时,

取最大值,用队列维护上凸壳,取最小值,用栈维护下凸壳

当x递减,可以当做斜率的方向全部改变了,然后方法和递增是相反的。

当k不单调时,就需要二分在凸壳上找答案了。

当x也不单调2时,cdq?反正本弱鸡不会

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值