洛谷 P4069 [SDOI2016]游戏 树链剖分+线段树

本文介绍了一种解决特定树形结构中路径操作问题的方法——树套线段树算法。该算法通过将树形结构拆分成多个区间并利用线段树进行区间更新和查询,有效地解决了游戏场景中路径上点的数值更新与最小值查询问题。

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

题目描述
Alice 和 Bob 在玩一个游戏。
游戏在一棵有 n 个点的树上进行。最初,每个点上都只有一个数字,那个数字是 123456789123456789。
有时,Alice 会选择一条从 s 到 t 的路径,在这条路径上的每一个点上都添加一个数字。对于路径上的一个点 r,若 r 与 s 的距离是 dis,那么 Alice 在点 r 上添加的数字是 a×dis+b。有时,Bob 会选择一条从 s 到 t 的路径。
他需要先从这条路径上选择一个点,再从那个点上选择一个数字。
Bob 选择的数字越小越好,但大量的数字让 Bob 眼花缭乱。Bob 需要你帮他找出他能够选择的最小的数字。

输入输出格式

输入格式:
第一行两个数字 n、m,表示树的点数和进行的操作数。
接下来 n−1 行,每行三个数字 u、v、w,表示树上有一条连接 u、v 的边,长度是 w。
接下来 m 行。每行第一个数字是 1 或 2。
若第一个数是 1,表示 Alice 进行操作,接下来四个数字 s、t、a、b。
若第一个数是 2,表示 Bob 进行操作,接下来四个数字 s、t。

输出格式:
每当 Bob 进行操作,输出一行一个数,表示他能够选择的最小的数字

输入输出样例

输入样例#1:
3 5
1 2 10
2 3 20
2 1 3
1 2 3 5 6
2 2 3
1 2 3 -5 -6
2 2 3
输出样例#1:
123456789123456789
6
-106

说明

测试点 1 ~ 2:n10m10a10000n≤10,m≤10,∣a∣≤10000
测试点 3 ~ 4:n1000m1000a10000n≤1000,m≤1000,∣a∣≤10000
测试点 5:n100000m100000a=0n≤100000,m≤100000,a=0,树是一条链;
测试点 6 ~ 7:n100000m100000a=0n≤100000,m≤100000,a=0
测试点 8:n100000m100000a=1n≤100000,m≤100000,a=1,树是一条链;
测试点 9 ~ 10:n100000m100000a=1n≤100000,m≤100000,a=1
测试点 11 ~ 13:n100000m100000a10000n≤100000,m≤100000,∣a∣≤10000,树是一条链;
测试点 14 ~ 20:n100000m100000a10000n≤100000,m≤100000,∣a∣≤10000

分析:
我们考虑一个1操作(x,y,A,B)(x,y,A,B)的影响。
显然,xxlca路径上的点ii多的权值为,

a[i]=(dis[x]dis[i])A+B

也就是,

a[i]=Adis[i]+(dis[x]A+B)a[i]=−A∗dis[i]+(dis[x]∗A+B)

lcalcayy的路径上点的权值为,
a[i]=Adis[i]+((dis[x]2dis[lca])A+B)

也就是两个一次函数。
也就是说,我们每次给一条路径上的每个点加一个一次函数,然后查询一条路径上每个点一次函数的最小值的最小值。
树套树不仅不现实,而且很浪费,因为每个点自变量不变。
因为是路径的操作,显然路径上的自变量单调。
算法就很好想了,我们把这些路径拆成loglog个区间,并给这些区间加上一条一次函数。而对于查询的每个区间[l,r][l,r],预处理出[lc,rc][lc,rc],表示这个区间的值域。因为只有可能在lclcrcrc处取得最小值,类似于二次函数。然后在线段树[l,r][l,r]区间内求出lclcrcrc的最小值。

代码:

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long

const int maxn=1e5+7;
const LL inf=123456789123456789;

using namespace std;

LL n,m,cnt,x,y,op;
LL ls[maxn],dfn[maxn],id[maxn],top[maxn],size[maxn],fa[maxn],dep[maxn];
LL dis[maxn];
LL w,A,B;

struct line{
    LL k,b;
    LL get(LL x)
    {
        return k*x+b;
    }
};

struct rec{
    LL lc,rc,ans;
    line c;
}t[maxn*4];

struct edge{
    LL y,next;
    LL w;
}g[maxn*2];

void add(LL x,LL y,LL w)
{
    cnt++;
    g[cnt].y=y;
    g[cnt].w=w;
    g[cnt].next=ls[x];
    ls[x]=cnt;
}

void dfs1(LL x,LL f)
{
    size[x]=1;
    fa[x]=f;
    dep[x]=dep[f]+1;
    for (LL i=ls[x];i>0;i=g[i].next)
    {
        LL y=g[i].y;
        if (y==f) continue;
        dis[y]=dis[x]+g[i].w;
        dfs1(y,x); 
        size[x]+=size[y];
    }
}

void dfs2(LL x,LL f)
{
    dfn[x]=++cnt;
    id[cnt]=x;
    top[x]=f;
    LL c=0;
    for (LL i=ls[x];i>0;i=g[i].next)
    {
        LL y=g[i].y;
        if (y==fa[x]) continue;
        if (size[y]>size[c]) c=y;
    }
    if (!c) return;
    dfs2(c,f);
    for (LL i=ls[x];i>0;i=g[i].next)
    {
        LL y=g[i].y;
        if ((y==fa[x]) || (y==c)) continue;
        dfs2(y,y);
    }
}

LL getlca(LL x,LL y)
{
    while (top[x]!=top[y])
    {
        if (dep[top[x]]>dep[top[y]]) swap(x,y);
        y=fa[top[y]];
    }
    if (dep[x]>dep[y]) swap(x,y);
    return x;
}

void build(LL p,LL l,LL r)
{
    t[p].c.k=0,t[p].c.b=inf;
    t[p].ans=inf;
    if (l==r)
    {
        t[p].lc=dis[id[l]];
        t[p].rc=dis[id[l]];
        return;
    }
    LL mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
    t[p].lc=min(t[p*2].lc,t[p*2+1].lc);
    t[p].rc=max(t[p*2].rc,t[p*2+1].rc);
}

void ins(LL p,LL l,LL r,line d)
{
    if (l==r)
    {
        if (t[p].c.get(t[p].lc)>d.get(t[p].lc)) t[p].c=d;
        t[p].ans=t[p].c.get(t[p].lc);
        return;
    }
    LL mid=(l+r)/2;
    LL lmid=t[p*2].rc;
    if (d.k>t[p].c.k)
    {
        if (d.get(lmid)<t[p].c.get(lmid))
        {
            ins(p*2+1,mid+1,r,t[p].c);
            t[p].c=d;
        }
        else ins(p*2,l,mid,d);
    }
    else
    {
        if (d.get(lmid)<t[p].c.get(lmid))
        {
            ins(p*2,l,mid,t[p].c);
            t[p].c=d;
        }
        else ins(p*2+1,mid+1,r,d);
    }
    LL tmp=min(t[p].c.get(t[p].lc),t[p].c.get(t[p].rc));
    t[p].ans=min(min(t[p*2].ans,t[p*2+1].ans),tmp);
}

void change(LL p,LL l,LL r,LL x,LL y,line d)
{
    if ((l==x) && (r==y))
    {
        ins(p,l,r,d);
        return;
    }
    LL mid=(l+r)/2;
    if (y<=mid) change(p*2,l,mid,x,y,d);
    else if (x>mid) change(p*2+1,mid+1,r,x,y,d);
    else
    {
        change(p*2,l,mid,x,mid,d);
        change(p*2+1,mid+1,r,mid+1,y,d);
    }
    LL tmp=min(t[p].c.get(t[p].lc),t[p].c.get(t[p].rc));
    t[p].ans=min(min(t[p*2].ans,t[p*2+1].ans),tmp);
}

void inslink(LL x,LL y,LL k,LL b)
{
    while (top[x]!=top[y])
    {
        if (dep[top[x]]>dep[top[y]]) swap(x,y);
        change(1,1,n,dfn[top[y]],dfn[y],(line){k,b});
        y=fa[top[y]];
    }
    change(1,1,n,dfn[x],dfn[y],(line){k,b});
}

LL query(LL p,LL l,LL r,LL x,LL y,LL &lc,LL &rc)
{
    if ((l==x) && (r==y))
    {
        lc=min(lc,t[p].lc);
        rc=max(rc,t[p].rc);
        return t[p].ans;
    }
    LL mid=(l+r)/2;
    LL tmp;
    if (y<=mid) tmp=query(p*2,l,mid,x,y,lc,rc);
    else if (x>mid) tmp=query(p*2+1,mid+1,r,x,y,lc,rc);
    else
    {
        LL lc1=inf,lc2=inf,rc1=0,rc2=0;
        tmp=min(query(p*2,l,mid,x,mid,lc1,rc1),query(p*2+1,mid+1,r,mid+1,y,lc2,rc2));
        lc=min(lc1,lc2);
        rc=max(rc1,rc2);
    }
    tmp=min(tmp,min(t[p].c.get(lc),t[p].c.get(rc)));
    return tmp;
}

LL getans(LL x,LL y)
{
    LL ans=inf,lc,rc;
    while (top[x]!=top[y])
    {
        if (dep[top[x]]>dep[top[y]]) swap(x,y);
        lc=inf,rc=0;
        ans=min(ans,query(1,1,n,dfn[top[y]],dfn[y],lc,rc));
        y=fa[top[y]];
    }
    if (dep[x]>dep[y]) swap(x,y);
    lc=inf,rc=0;
    ans=min(ans,query(1,1,n,dfn[x],dfn[y],lc,rc));
    return ans;
}

int main()
{
    scanf("%lld%lld",&n,&m);
    for (LL i=1;i<n;i++)
    {
        scanf("%lld%lld%lld",&x,&y,&w);
        add(x,y,w);
        add(y,x,w); 
    }
    dfs1(1,0);
    cnt=0;
    dfs2(1,0);
    build(1,1,n);   
    for (LL i=1;i<=m;i++)
    {
        scanf("%lld",&op);
        if (op==1)
        {
            scanf("%lld%lld%lld%lld",&x,&y,&A,&B);
            LL lca=getlca(x,y);
            inslink(lca,x,-A,dis[x]*A+B);
            inslink(lca,y,A,(dis[x]-2*dis[lca])*A+B);
        }
        else
        {               

            scanf("%lld%lld",&x,&y);            
            printf("%lld\n",getans(x,y));
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值