bzoj4515 [Sdoi2016]游戏(树链剖分+超哥线段树)

题目链接

分析:
这道题要是让我做,就是模拟暴力
我们的目的是通过这道题学习一下超哥线段树

在树上的区间修改,维护最小值
可以立刻联想到树链剖分用线段树维护最小值

但是这个 dis 让人很不爽啊
显然s~t的路径可以分成s~lca和lca~t两条路径
deep[x] 表示 x 的深度,
对于s~lca上面的点x,修改的值相当于 a(deep[s]deep[x])+b=adeep[x]+(adeep[s]+b)
lca~t上面的点 x 的值相当于a(deep[s]+deep[x]2deep[lca])+b=adeep[x]+(b+a(deep[s]deep[lca]2))

这样就可以得到新的 a b ,然后一个点的值就相当于 adeep[x]+b 了,
这样就只和 a b deep[x] 有关了

在树链剖分对应的线段树区间中,相连的一部分ta们在树中也是相连的,这样就满足 deep[] 单调递增,
观察 ad[x]+b ,发现这相当于一条直线 f(x)=ax+b
这就相当于用线段树维护一个区间中的若干条直线在每个部分的最小值

这种数据结构就叫做:超哥线段树

常见的处理方式就是标记永久化(这里只是一定程度上的永久化但还是要下传的)
让线段树中的一个结点对应一条直线
那么如果在这个区间加入一条直线怎么办呢?
分类讨论
设新加入的 f1(x)=k1x+b1 ,原来的 f2(x)=k2x+b2
左端点为 l ,右端点为r,那么有:

  1. f1(deep[l])<f2(deep[l]) f1(deep[r])<f2(deep[r])
    对应一条直线在两个端点都比另一条小,那么显然在l~r中 f1(x) 处处比 f2(x) 小,直接把 f2(x) 替换为 f1(x)

  2. 同理若上式的两个符号都为 > ,那么f1(x)处处不如 f2(x) 优,不做更改

  3. k1<k2 ,由于不满足1.2,显然两条直线有交点
    此时解不等式 f1(x)<f2(x) 得到 x>(b1b2)/(k2k1) ,判断 (b1b2)/(k2k1) 在左半区间还是右半区间递归下传即可

  4. k1>k2 同理

询问就简单多了,直接用标记永久化的线段树的方法更新即可

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

const ll INF=123456789123456789;
const int N=100005;
struct node{
    int x,y,v,nxt;
};
node way[N<<1];
int st[N],tot=0;
struct Tree{
    ll a,b,mn,sl,sr;
};
Tree T[N<<2];
ll deep[N],a,b;
int pre[N],top[N],n,m,num[N],shu[N],son[N],tt=0,sz[N],clo=0;
int x,y;

void add(int u,int w,int z)
{
    tot++;
    way[tot].x=u;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
    tot++;
    way[tot].x=w;way[tot].y=u;way[tot].v=z;way[tot].nxt=st[w];st[w]=tot;
}

void dfs_1(int now,int fa,int dep)
{
    deep[now]=dep; pre[now]=fa;
    sz[now]=1;
    int maxx=0;
    for (int i=st[now];i;i=way[i].nxt)
        if (way[i].y!=fa)
        {
            dfs_1(way[i].y,now,dep+way[i].v);
            sz[now]+=sz[way[i].y];
            if (sz[way[i].y]>maxx)
            {
                maxx=sz[way[i].y];
                son[now]=way[i].y;
            }
        }  
}

void dfs_2(int now,int fa)
{
    if (son[fa]!=now) top[now]=now;
    else top[now]=top[fa];
    num[now]=++clo; shu[num[now]]=now;
    if (son[now])
    {
        dfs_2(son[now],now);
        for (int i=st[now];i;i=way[i].nxt)
            if (way[i].y!=fa&&way[i].y!=son[now])
                dfs_2(way[i].y,now);
    }
}

void build(int bh,int l,int r)
{
    T[bh].mn=T[bh].b=INF;
    if (l==r)
    {
        T[bh].sl=T[bh].sr=deep[shu[l]];    //l在树中的编号 
        return;
    }
    int mid=(l+r)>>1;
    build(bh<<1,l,mid); 
    build((bh<<1)+1,mid+1,r);
    T[bh].sl=T[bh<<1].sl; 
    T[bh].sr=T[(bh<<1)+1].sr;
}

int lca(int x,int y)
{
    for (;top[x]!=top[y];x=pre[top[x]])
        if (deep[top[x]]<deep[top[y]]) swap(x,y);
    return (deep[x]<deep[y])? x:y;
}

void change(int bh,int l,int r,int L,int R,ll a,ll b)
{
    int mid=(l+r)>>1;
    if (l>=L&&r<=R)
    {
        ll z1=T[bh].sl*T[bh].a+T[bh].b;     //左右端点的f值 
        ll z2=T[bh].sr*T[bh].a+T[bh].b;
        ll z3=T[bh].sl*a+b;
        ll z4=T[bh].sr*a+b; 

        if (z3<z1&&z4<z2) 
            T[bh].a=a,T[bh].b=b;
        else if ((z3<z1&&z4>z2)||(z3>z1&&z4<z2))
            change(bh<<1,l,mid,L,R,a,b),
            change((bh<<1)+1,mid+1,r,L,R,a,b);

        T[bh].mn=min(T[bh].mn,min(T[bh].sl*a+b,T[bh].sr*a+b));      //维护区间最小值 
        if (l!=r) T[bh].mn=min(T[bh].mn,min(T[bh<<1].mn,T[(bh<<1)+1].mn));  //update 
        return;
    }
    if (L<=mid) change(bh<<1,l,mid,L,R,a,b);
    if (R>mid) change((bh<<1)+1,mid+1,r,L,R,a,b);
    T[bh].mn=min(T[bh].mn,min(T[bh<<1].mn,T[(bh<<1)+1].mn));
    return;
}

void modify()
{
    scanf("%d%d%d%d",&x,&y,&a,&b);

    ll c=deep[x]*a+b,d=(deep[x]-2*deep[lca(x,y)])*a+b;
    ll ans=INF;
    int f1=top[x],f2=top[y];
    while (f1!=f2)
    {
        if (deep[f1]>deep[f2]) 
            change(1,1,n,num[f1],num[x],-a,c),x=pre[f1];   //s~lca 
        else 
            change(1,1,n,num[f2],num[y],a,d),y=pre[f2];    //lca~t
        f1=top[x];
        f2=top[y];
    }
    if (deep[x]>deep[y]) 
        change(1,1,n,num[y],num[x],-a,c);
    else
        change(1,1,n,num[x],num[y],a,d);
}

ll ask(int bh,int l,int r,int L,int R)
{
    if (l>=L&&r<=R) 
        return T[bh].mn;

    ll ans=min(deep[shu[L]]*T[bh].a+T[bh].b,deep[shu[R]]*T[bh].a+T[bh].b); 

    int mid=(l+r)>>1;
    if (L<=mid) ans=min(ans,ask(bh<<1,   l,    mid,L,           min(mid,R)));
    if (R>mid) ans=min(ans,ask((bh<<1)+1,mid+1,r,  max(mid+1,L),R));
    return ans;
}

void askans()
{
    scanf("%d%d",&x,&y);

    ll ans=INF;
    int f1=top[x],f2=top[y];
    while (f1!=f2)
    {
        if (deep[f1]<deep[f2]) swap(x,y),swap(f1,f2);
        ans=min(ans,ask(1,1,n,num[f1],num[x]));
        x=pre[f1];
        f1=top[x];
    }
    if (num[x]>num[y]) swap(x,y);
    ans=min(ans,ask(1,1,n,num[x],num[y]));
    printf("%lld\n",ans);
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<n;i++)
    {
        int u,w,z;
        scanf("%d%d%d",&u,&w,&z);
        add(u,w,z);
    }
    dfs_1(1,0,0); dfs_2(1,0);
    build(1,1,n);

    int opt;
    for (int i=1;i<=m;i++)
    {
        scanf("%d",&opt);
        if (opt==1) modify();
        else askans();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值