bzoj 4515 [Sdoi2016]游戏 线段树维护凸包

本文介绍了一种利用树链剖分将路径操作问题转化为区间问题的方法,并通过线段树维护区间上的最值,实现了对路径上每个节点进行高效更新与查询的功能。

先树链剖分转成区间问题。
S到T的路径可以拆成S-lca和lca-T。
设d[r]表示根到r的距离。
对于S-lca中的点r,加的值为:
a(d[s]d[r])+b=(a)d[r]+(ad[s]+b)
对于lca-T中的点r,加的值为:
a(d[r]+d[s]2d[lca])+b=ad[r]+(ad[s]+b2ad[lca])
这样就可以转成将一个区间的每个点r的值与一次函数在d[r]处的值取min。

在线段树上维护这个东西。
线段树上每个点维护一条跨过当前点的线段。
修改一段区间时对于线段树上受影响的点,如果在当前区间,增加的线段与当前线段没有交点那么将当前线段修改为在上面的那条。
否则求交点,然后往下递归传一条线段。
注意由于区间查询所以需要pushdown

区间查询时每到一个区间用当前区间的直线更新答案,然后正常往下查询。

#include <bits/stdc++.h>
using namespace std;
#define N 110000
#define ll long long
#define ls l,mid,now<<1
#define rs mid+1,r,now<<1|1
#define inf 123456789123456789ll
int n,m,tot,cnt;
int head[N],nex[N<<1],to[N<<1],val[N<<1];
int deep[N],fa[N],son[N],top[N],pos[N],bel[N],size[N];
ll d[N],v[N<<3];
struct node
{
    ll a,b;
    node(){}
    node(ll a,ll b):a(a),b(b){}
    ll cal(ll x){return a*x+b;}
}tr[N<<3],v1;
double ins(node r1,node r2)
{return (double)(r2.b-r1.b)/(r1.a-r2.a);}
void add(int x,int y,int z)
{
    tot++;
    nex[tot]=head[x];head[x]=tot;
    to[tot]=y;val[tot]=z;
}
void dfs1(int x,int y)
{
    size[x]=1;fa[x]=y;
    deep[x]=deep[y]+1;
    for(int i=head[x];i;i=nex[i])
        if(to[i]!=y)
        {
            d[to[i]]=d[x]+val[i];
            dfs1(to[i],x);
            size[x]+=size[to[i]];
            son[x]=size[to[i]]>size[son[x]] ? to[i]:son[x];
        }       
}
void dfs2(int x,int y,int tp)
{
    top[x]=tp;
    pos[x]=++cnt;bel[cnt]=x;
    if(son[x])dfs2(son[x],x,tp);

    for(int i=head[x];i;i=nex[i])
        if(to[i]!=y&&to[i]!=son[x])
            dfs2(to[i],x,to[i]);
}
void build(int l,int r,int now)
{
    tr[now]=node(0,inf);
    v[now]=inf;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(ls);build(rs);
}
int lca(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(deep[top[x]]<deep[top[y]])swap(x,y);
        x=fa[top[x]];
    }
    return deep[x]<deep[y] ? x:y;
}
void pushup(int x)
{
    v[x]=min(v[x],v[x<<1]);
    v[x]=min(v[x],v[x<<1|1]);
}
void insert(int l,int r,int now,node v1)
{
    ll lv=v1.cal(d[bel[l]]),rv=v1.cal(d[bel[r]]);
    ll lp=tr[now].cal(d[bel[l]]),rp=tr[now].cal(d[bel[r]]);
    if(lv>=lp&&rv>=rp)return;
    if(lv<=lp&&rv<=rp)tr[now]=v1;
    else
    {
        int mid=(l+r)>>1;
        double x=ins(v1,tr[now]),t=(double)(d[bel[mid]]+d[bel[mid+1]])/2.0;
        if(lv>lp)
        {
            if(x>=t)insert(rs,v1);
            else insert(ls,tr[now]),tr[now]=v1;
        }
        else
        {
            if(x<=t)insert(ls,v1);
            else insert(rs,tr[now]),tr[now]=v1;
        }
    }
    v[now]=min(v[now],tr[now].cal(d[bel[l]]));
    v[now]=min(v[now],tr[now].cal(d[bel[r]]));
    pushup(now);
}
void update(int l,int r,int now,int lq,int rq)
{
    if(lq<=l&&r<=rq)
        {insert(l,r,now,v1);return;}
    int mid=(l+r)>>1;
    if(mid>=lq)update(ls,lq,rq);
    if(mid<rq) update(rs,lq,rq);
    pushup(now);
}
void update(int x,int y)
{
    while(top[x]!=top[y])
    {
        update(1,n,1,pos[top[x]],pos[x]);
        x=fa[top[x]];
    }
    update(1,n,1,pos[y],pos[x]);
}
ll get(int l,int r,int now,int lq,int rq)
{
    l=max(l,lq);r=min(r,rq);
    return min(tr[now].cal(d[bel[l]]),tr[now].cal(d[bel[r]]));
}
ll query(int l,int r,int now,int lq,int rq)
{
    if(lq<=l&&r<=rq)return v[now];
    int mid=(l+r)>>1;
    ll ret=get(l,r,now,lq,rq);
    if(mid>=lq)ret=min(ret,query(ls,lq,rq));
    if(mid<rq) ret=min(ret,query(rs,lq,rq));
    return ret;
}
ll query(int x,int y)
{
    ll ret=inf;
    while(top[x]!=top[y])
    {
        if(deep[top[x]]<deep[top[y]])swap(x,y);
        ret=min(ret,query(1,n,1,pos[top[x]],pos[x]));
        x=fa[top[x]];
    }
    if(deep[x]<deep[y])swap(x,y);
    ret=min(ret,query(1,n,1,pos[y],pos[x]));
    return ret;
}
int main()
{
    memset(v,127,sizeof(v));
    scanf("%d%d",&n,&m);
    for(int i=1,x,y,v;i<n;i++)
    {
        scanf("%d%d%d",&x,&y,&v);
        add(x,y,v);add(y,x,v);
    }
    dfs1(1,0);
    dfs2(1,0,1);
    build(1,n,1);
    for(int tp,s,t,a,b;m--;)
    {
        scanf("%d",&tp);
        if(tp==1)
        {
            scanf("%d%d%d%d",&s,&t,&a,&b);
            int lc=lca(s,t);
            v1=node(-a,a*d[s]+b);update(s,lc);
            v1=node(a,a*d[s]+b-2*a*d[lc]);update(t,lc);
        }
        else
        {
            scanf("%d%d",&s,&t);
            printf("%lld\n",query(s,t));
        }
    }
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值