BZOJ4515: [Sdoi2016]游戏-树链剖分+超哥线段树

传送门

题意:

Alice 和 Bob 在玩一个游戏。

游戏在一棵有 n 个点的树上进行。最初,每个点上都只有一个数字,那个数字是 123456789123456789。

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

Solution:

首先考虑树是一条链的情况:

我们可以把等差序列看成一个一次函数,然后转化一下题意:

1.区间插入一条线段

2.求区间内最靠下的点

显然可以用超哥线段树来解决

因为每条边的长度不同,所以我们需要引入一个dis数组,dis[i]表示i距根节点的距离

我们对于每个线段树节点所代表的区间维护一条线段,表示在这个区间的所有最小值点中,有点在这条线段上

考虑在一个区间 [l,r] [ l , r ] 中已经有一条线段f1,现在我们再加入一条线段f2,求出f1的在l的取值l1,在r的取值r1和f2在l,r的取值l2,r2,那么我们就可以分类讨论:

1.l1<=l2,r1<=r2 显然f2没有任何点会比f1优,直接退出即可

2.l1>l2 r1>r2 用f2覆盖f1即可

3.l1>l2 r1<=r2

我们求出f1,f2在mid处的取值m1,m2,再分类讨论:

(1)m1>m2 说明在左半边都是f2更优,对于整个区间,用f2覆盖f1,在右半边讨论f1

(2)m1<=m2 说明在右半边都是f1更优,左半边讨论f2即可

4.l1<=l2 r1>r2 类似第三种情况讨论即可

链上问题转到树上,树链剖分即可

在这里还需要用到标记永久化的技巧,即我们不下传标记,在求区间最小值的时候,每次访问到一个和询问区间有交集的区间,把答案和这个区间所维护的线段取min,具体细节请看代码

代码:

#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;
const int N=100010;
const long long inf=123456789123456789; 
int n,m;
struct edg{
    int to,next,v;
}e[2*N];
struct tree{
    int l,r;
    long long x,y,v;
    bool tag;
}tr[4*N];
bool vis[N];
long long ans,dis[N];
int head[N],top[N],size,dep[N],hs[N],sz[N],f[N][31],dfn[N],cnt,p[N];
void add(int x,int y,int v){size++;e[size]={y,head[x],v};head[x]=size;}
void dfs1(int x,int fa)
{
    dep[x]=dep[fa]+1;sz[x]=1;f[x][0]=fa;
    for (int i=head[x];i;i=e[i].next)
    {
        int y=e[i].to;
        if (dep[y]) continue;
        dis[y]=dis[x]+e[i].v;
        dfs1(y,x);
        sz[x]+=sz[y];
        if (sz[y]>sz[hs[x]]) hs[x]=y;
    }
}
void dfs2(int x,int ff)
{
    top[x]=ff;dfn[x]=++cnt;vis[x]=1;p[cnt]=x;
    if (hs[x]) dfs2(hs[x],ff);
    for (int i=head[x];i;i=e[i].next)
    {
        int y=e[i].to;
        if (vis[y]) continue;
        dfs2(y,y);
    }
}
void build(int i,int l,int r)
{
    tr[i].l=l,tr[i].r=r;tr[i].v=inf;
    if (l==r) return;
    int mid=l+r>>1;
    build(i<<1,l,mid);build(i<<1|1,mid+1,r);
}
int LCA(int x,int y)
{
    if (dep[x]<dep[y]) swap(x,y);
    for (int i=30;i>=0;i--) if (dep[f[x][i]]>=dep[y]) x=f[x][i];
    if (x==y) return x;
    for (int i=30;i>=0;i--) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
void update(int i){tr[i].v=min(tr[i].v,min(tr[i<<1].v,tr[i<<1|1].v));}
void add(int i,ll a,ll b)
{
    int L=tr[i].l,R=tr[i].r;
    if (!tr[i].tag) {tr[i].tag=1,tr[i].x=a,tr[i].y=b,tr[i].v=min(tr[i].v,min(1ll*dis[p[L]]*b+a,1ll*dis[p[R]]*b+a));return;}
    ll l1=tr[i].y*dis[p[L]]+tr[i].x;
    ll l2=b*dis[p[L]]+a;
    ll r1=tr[i].y*dis[p[R]]+tr[i].x;
    ll r2=b*dis[p[R]]+a;
    if (l2>=l1&&r2>=r1) return;
    if (l2<l1&&r2<r1) {tr[i].x=a,tr[i].y=b;tr[i].v=min(tr[i].v,min(1ll*dis[p[L]]*b+a,1ll*dis[p[R]]*b+a));return;}
    int mid=L+R>>1;
    ll m1=tr[i].y*dis[p[mid]]+tr[i].x;
    ll m2=b*dis[p[mid]]+a;
    if (l2>=l1)
    {
        if (m2>=m1) add(i<<1|1,a,b);
        else add(i<<1,tr[i].x,tr[i].y),tr[i].x=a,tr[i].y=b,tr[i].v=min(tr[i].v,min(1ll*dis[p[L]]*b+a,1ll*dis[p[R]]*b+a));
    }
    else
    {
        if (m2>=m1) add(i<<1,a,b);
        else add(i<<1|1,tr[i].x,tr[i].y),tr[i].x=a,tr[i].y=b,tr[i].v=min(tr[i].v,min(1ll*dis[p[L]]*b+a,1ll*dis[p[R]]*b+a));
    }
    update(i);
}
void modify(int i,int l,int r,ll a,ll b)
{
    int L=tr[i].l,R=tr[i].r;
    if (L>r||l>R) return;
    if (l<=L&&R<=r) {add(i,a,b);return;}
    modify(i<<1,l,r,a,b);modify(i<<1|1,l,r,a,b);
    update(i);
}
void query(int i,int l,int r)
{
    int L=tr[i].l,R=tr[i].r;
    if (L>r||l>R) return;
    if (l<=L&&R<=r) {ans=min(ans,tr[i].v);return;}
    if (tr[i].tag) ans=min(ans,min(1ll*dis[p[max(L,l)]]*tr[i].y+tr[i].x,1ll*dis[p[min(R,r)]]*tr[i].y+tr[i].x));
    query(i<<1,l,r);query(i<<1|1,l,r);
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int x,y,v,i=1;i<n;i++) scanf("%d%d%d",&x,&y,&v),add(x,y,v),add(y,x,v);
    dfs1(1,0);dfs2(1,1);
    build(1,1,n);
    for (int i=1;i<=30;i++)
        for (int j=1;j<=n;j++)
            f[j][i]=f[f[j][i-1]][i-1];
    long long a,b;
    for (int x,y,z,i=1;i<=m;i++)
    {
        scanf("%d%d%d",&z,&x,&y);
        if (z==1)
        {
            scanf("%lld%lld",&b,&a);
            int xx=x;
            int lca=LCA(x,y);
            ll na=a+b*dis[x];
            ll nb=-b;
            while (top[x]!=top[lca])
            {
                modify(1,dfn[top[x]],dfn[x],na,nb);
                x=f[top[x]][0];
            }
            modify(1,dfn[lca],dfn[x],na,nb);
            na=a+b*(dis[xx]-dis[lca])-b*dis[lca];       
            nb=b;
            while (top[y]!=top[lca])
            {
                modify(1,dfn[top[y]],dfn[y],na,nb);
                y=f[top[y]][0];
            }
            modify(1,dfn[lca],dfn[y],na,nb);
        }
        else
        {
            ans=inf;
            while (top[x]!=top[y])
            {
                if (dep[top[x]]<dep[top[y]]) swap(x,y);
                query(1,dfn[top[x]],dfn[x]);
                x=f[top[x]][0];
            }
            if (dep[x]<dep[y]) swap(x,y);
            query(1,dfn[y],dfn[x]);
            printf("%lld\n",ans);
        }
    }
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值