bzoj4515: [Sdoi2016]游戏

链接

  http://www.lydsy.com/JudgeOnline/problem.php?id=4515

题解

  这是一道神题!
  对于一次修改(s,t,a,b),设s和t的最近公共祖先是lca。可以推出,当x节点在s到lca的路径上时,x节点上要添加的数字就是 adist[x]+adist[s]+b ;当x节点在lca到t的路径上时,这个节点要添加的数字是 adist[x]+adist[s]2adist[lca]+b
  树剖转成序列,问题就变成了:支持给一个区间添加一条线段,询问区间最小值。
  使用线段树来维护。
  每一个节点上,都有且仅有一条线段,这条线段用a、b、dl、dr来描述,分别表示斜率、截距、左端点和右端点的自变量。
  可以使用标记永久化,即没有下放标记的操作。线段树中记录了子树的最小值,使用传统的线段树查询时,当l<=p->l且r>=p->r时,应当直接返回所记录的子树中的最小值。否则,由于查询区间不完全包含当前区间,所以应该取max(l,p->l)和min(r,p->r)处的自变量在当前节点所对应的函数上的取值,让之参与最小值的比较。(主要是这里想了一天)
  其实算法的重点在于,怎样合并两条线段。
  case1:如果新线段在当前节点的定义域中,完全小于原来的线段,那应当舍去原来的线段,并且用当前线段的两个端点更新最小值。
  case2:新线段在当前节点的定义域中,完全大于原来的线段,应当舍去这条新线段。
  case3:新加入的线段和原来的线段在当前的定义域中有交点。求一下两条线段的交点横坐标x。如果新线段的左端点大于旧线段的左端点,或x<=mid,就向左下放;如果新线段的右端点小于旧线段的右交点,或x>mid,就往右下放。
  OK.

代码

//树链剖分
#include <cstdio>
#include <algorithm>
#include <iostream>
#define ll long long
#define inf 123456789123456789ll
#define maxn 210000
#define maxk 17
using namespace std;
ll N, M, head[maxn], next[maxn], w[maxn], to[maxn], top[maxn], tid[maxn], tim,
    dist[maxn], size[maxn], son[maxn], fa[maxn], tot, anc[maxn][maxk+3],
    deep[maxn], untid[maxn];
struct segtree
{
    ll l, r, min, taga, tagb, dl, dr;
    segtree *lch, *rch;
    segtree(){min=inf;lch=rch=0;taga=0;tagb=inf;}
}*root;
void adde(ll a, ll b, ll v){to[++tot]=b;w[tot]=v;next[tot]=head[a];head[a]=tot;}
ll f(ll a, ll b, ll x){return a*x+b;}
void addline(segtree *p, ll taga, ll tagb)
{
    ll f1, f2, f3, f4, x, mid;
    f1=f(p->taga,p->tagb,p->dl),f2=f(p->taga,p->tagb,p->dr);
    f3=f(taga,tagb,p->dl),f4=f(taga,tagb,p->dr);
    if(f3<=f1 and f4<=f2)
    {
        p->taga=taga,p->tagb=tagb;
        p->min=min(min(f3,f4),p->min);
        return;
    }
    if(f3>f1 and f4>f2)return;
    x=(tagb-p->tagb)/(p->taga-taga);
    mid=p->lch->dr;
    if(f3<=f1 or x<=mid)addline(p->lch,taga,tagb);
    if(f4<=f2 or x>mid)addline(p->rch,taga,tagb);
    p->min=min(p->min,min(p->lch->min,p->rch->min));
}
void segtag(segtree *p, ll l, ll r, ll a, ll b)
{
    ll mid=(p->l+p->r)>>1;
    if(l<=p->l and r>=p->r)
    {
        addline(p,a,b);
        return;
    }
    if(l<=mid)segtag(p->lch,l,r,a,b);
    if(r>mid)segtag(p->rch,l,r,a,b);
    p->min=min(p->min,min(p->lch->min,p->rch->min));
}
ll segmin(segtree *p, ll l, ll r)
{
    ll mid=(p->l+p->r)>>1, ans=inf;
    if(l<=p->l and r>=p->r)return p->min;
    if(l<=mid)ans=min(ans,segmin(p->lch,l,r));
    if(r>mid)ans=min(ans,segmin(p->rch,l,r));
    l=max(l,p->l), r=min(r,p->r);
    ans=min(ans,f(p->taga,p->tagb,dist[untid[l]]));
    ans=min(ans,f(p->taga,p->tagb,dist[untid[r]]));
    return ans;
}
void build(segtree *p, ll l, ll r)
{
    ll mid=(l+r)>>1;
    p->l=l,p->r=r;
    if(l==r){p->dl=p->dr=dist[untid[l]];return;}
    build(p->lch=new segtree,l,mid);
    build(p->rch=new segtree,mid+1,r);
    p->dl=p->lch->dl;p->dr=p->rch->dr;
}
void dfs1(ll pos)
{
    ll p, v;
    size[pos]=1;
    for(p=head[pos];p;p=next[p])
    {
        if((v=to[p])==fa[pos])continue;
        fa[v]=pos;
        dist[v]=dist[pos]+w[p];
        deep[v]=deep[pos]+1;
        dfs1(v);
        if(size[v]>size[son[pos]])son[pos]=v;
        size[pos]+=size[v];
    }
}
void dfs2(ll pos, ll tp)
{
    ll p, v;
    top[pos]=tp;
    tid[pos]=++tim;
    untid[tid[pos]]=pos;
    if(son[pos])dfs2(son[pos],tp);
    for(p=head[pos];p;p=next[p])
        if((v=to[p])!=fa[pos] and v!=son[pos])dfs2(v,v);
}
ll findmin(ll a, ll b)
{
    ll ta=top[a], tb=top[b], ans=inf;
    while(ta!=tb)
    {
        if(deep[ta]<deep[tb])swap(a,b),swap(ta,tb);
        ans=min(ans,segmin(root,tid[ta],tid[a]));
        a=fa[ta];ta=top[a];
    }
    if(deep[a]>deep[b])swap(a,b);
    ans=min(ans,segmin(root,tid[a],tid[b]));
    return ans;
}
void maketag(ll a, ll b, ll taga, ll tagb)
{
    ll ta=top[a], tb=top[b];
    while(ta!=tb)
    {
        if(deep[ta]<deep[tb])swap(a,b),swap(ta,tb);
        segtag(root,tid[ta],tid[a],taga,tagb);
        a=fa[ta];ta=top[a];
    }
    if(deep[a]>deep[b])swap(a,b);
    segtag(root,tid[a],tid[b],taga,tagb);
}
void calcf()
{
    ll i, k;
    for(i=1;i<=N;i++)anc[i][0]=fa[i];
    for(k=1;k<=maxk;k++)
        for(i=1;i<=N;i++)
            anc[i][k]=anc[anc[i][k-1]][k-1];
}
ll LCA(ll x, ll y)
{
    ll k;
    if(deep[x]<deep[y])swap(x,y);
    for(k=maxk;k>=0;k--)if(deep[anc[x][k]]>=deep[y])x=anc[x][k];
    if(x==y)return x;
    for(k=maxk;k>=0;k--)if(anc[x][k]!=anc[y][k])x=anc[x][k],y=anc[y][k];
    return anc[x][0];
}
void solve()
{
    ll type, s, t, a, b, i, lca;
    for(i=1;i<=M;i++)
    {
        scanf("%lld%lld%lld",&type,&s,&t);
        if(type==1)
        {
            scanf("%lld%lld",&a,&b);
            lca=LCA(s,t);
            maketag(s,lca,-a,a*dist[s]+b);
            maketag(lca,t,a,a*dist[s]-2*a*dist[lca]+b);
        }
        else printf("%lld\n",findmin(s,t));
    }
}
void init()
{
    ll i, a, b, v;
    scanf("%lld%lld",&N,&M);
    for(i=1;i<N;i++)scanf("%lld%lld%lld",&a,&b,&v),adde(a,b,v),adde(b,a,v);
    deep[1]=1;
    dist[1]=0;
    dfs1(1);
    dfs2(1,1);
    calcf();
    build(root=new segtree,1,N);
}
int main()
{
    init();
    solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值