斜率优化理解【16年 沈阳区域赛 The Elder 】

举个例子来理解

16年沈阳区域赛【树形dp+斜率优化】

题意:

给你n个点,n-1条边的树。每条边有一个权值w。给你一个值p。

1号节点为根节点。求1号点到所有节点的路径中的最小权值  的最大值。

权值计算方法:将这条路可以一次走完,权值是(dis[u]-dis[1])*(dis[u]-dis[1]),也可以分段走完,每经过一个点权值加p,假如经过一个点x,那么权值可以是(dis[u]-dis[x])*(dis[u]-dis[x])+p+(dis[x]-dis1])*(dis[x]-dis1])

如果一个一个找分段的点就是O(N^2),那么可以用斜率优化的方法

终点是u,如果v点比w点作为暂停的点更优,那么dp[i]是到达i的的最小的权值

   dp[v]+p+(dis[u]-dis[v])*(dis[u]-dis[v])<=dp[w]+p+(dis[u]-dis[w])*(dis[u]-dis[w]);

dp[v]+dis[u]^2+dis[v]^2-2*dis[u]*dis[v]<=dp[w]+dis[u]^2+dis[w]^2-2*dis[u]*dis[w];

             dp[v]+dis[v]^2-2*dis[u]*dis[v]<=dp[w]+dis[w]^2-2*dis[u]*dis[w];

             dp[v]+dis[v]^2-dp[w]-dis[w]^2<=2*dis[u]*(dis[v]-dis[w]);

不妨假设f[x]=dp[x]+dis[x]^2;

那么 f[v]-f[w]<=2*dis[u]*(dis[v]-dis[w]);

假设某点是(dis[x],f[x]),那么就是斜率问题了,当且仅当v>w且时v点更新u,比w点更新w优。

这时候就需要维护凸包了:

维护凸包(下凸)原因:

假设存在这样的三个点

,很明显他们存在这样的关系

那么他们跟2*dis[u]会存在三种可能的关系:

1.,此时i比j优,j比k优

2.此时i比j优,k比j优

3.此时j比i优,k比j优

综上所述j不会是最优,所以要维护一个凸包(下凸)。

假如

每次在得到一个新的状态的时候,由于dis[]的单调性,它的位置必然是在这个半凸壳的右端处。由于dis[]的单调性,f[]也是满足单调递增,这样就可以用一个单调队列来维护半凸壳上的点。

队头的维护:(当l和(l+1)斜率小于2*dis[u],删掉l,因为(l+1)更优)

while(l<r&&gety(q[l+1],q[l])<=2*dis[u]*getx(q[l+1],q[l])) l++;

队尾维护:(维护斜率的增长,此时这三个点从左到右(r-1),r,u,如果不是单调递增的,r不会是优的,所以删掉)

    while(l<r&&getx(u,q[r])*gety(q[r],q[r-1])>=gety(u,q[r])*getx(q[r],q[r-1])) r--;

1. 检查队头的两个元素q[l]和q[l+1],通过上面的斜率检查,如果q[l+1]比q[l]更优,那么就把q[l]出队。

2. 直接取队头的元素为目标状态,进行状态转移,计算出f[u]。

3. 将u插入队尾。插入之前需要检查三个状态q[r-1], q[r], u是否满足斜率单调递增,若不满足则将q[r]出队。

需要注意的是,由于每个节点可能有多个子节点,因此每次转移之后要将队尾恢复为原来的元素。

 

代码:

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f3f3f3f3fLL
using namespace std;
const int maxn=200010;
int n,m,k,x,y,s;
ll ans,tmp,cnt,p,aa;
ll zt[maxn],l,r;
struct node
{
    int to,nex;
    ll w;
}a[maxn];
int he[maxn],tot,q[maxn];
ll dp[maxn],dis[maxn];
void add(int u,int v,ll w)
{
    a[tot].to=v;
    a[tot].w=w;
    a[tot].nex=he[u];
    he[u]=tot++;
}
void init()
{
    tot=r=0;l=1;
    memset(he,-1,sizeof(he));
    memset(dis,0,sizeof(dis));
    ans=0;dp[1]=q[0]=0;
}
ll gety(int u,int v)
{
    return dp[u]+dis[u]*dis[u]-dp[v]-dis[v]*dis[v];
}
ll getx(int u,int v){return dis[u]-dis[v];}
ll getdp(int u,int v){return dp[v]+p+(dis[u]-dis[v])*(dis[u]-dis[v]);}
 
void getpre(int u,int fa)
{
    for(int i=he[u];i!=-1;i=a[i].nex)
    {
        int v=a[i].to;
        if(v==fa) continue;
        dis[v]=dis[u]+a[i].w;
       // cout<<v<<" "<<dis[v]<<endl;
        getpre(v,u);
    }
}
void dfs(int u,int fa,int l,int r)
{
    int pre=-1;
    while(l<r&&gety(q[l+1],q[l])<=2*dis[u]*getx(q[l+1],q[l])) l++;
    //cout<<dp[u]<<endl;
    dp[u]=min(dp[u],getdp(u,q[l]));
    while(l<r&&getx(u,q[r])*gety(q[r],q[r-1])>=gety(u,q[r])*getx(q[r],q[r-1])) r--;
    pre=q[++r];q[r]=u;
    ans=max(ans,dp[u]);
    for(int i=he[u];i!=-1;i=a[i].nex)
    {
        int v=a[i].to;
        if(v==fa) continue;
        dfs(v,u,l,r);
    }
    if(pre!=-1) q[r]=pre;//恢复队尾
}
int main()
{
    int T,cas=1;
    scanf("%d",&T);
    while(T--)
    {
       scanf("%d%lld",&n,&p);
        init();
        for(int i=0;i<n-1;i++)
        {
            scanf("%d%d%lld",&x,&y,&aa);
            add(x,y,aa);
            add(y,x,aa);
        }
        getpre(1,-1);
        for(int i=1;i<=n;i++)
        dp[i]=dis[i]*dis[i];
        dfs(1,-1,1,0);
        printf("%lld\n",ans);
      //  if(flag) puts("Yes"); else puts("No");
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值