[ZOJ3522]Hide and seek-Link Cut Tree

Hide and seek

Flandre Scarlet loves her elder sister Remilia Scarlet, of course, Remilia loves Flandre, too. But because of Flandre’s unstable personality and incredible destructive power, Remilia has ordered Flandre not to leave Scarlet Devil Mansion for nearly 500 years. The life of Flandre is so boring that it’s no surprising that she did some unimaginable things. To solve this problem, Remilia decides to play a famous game in Scarlet Devil Mansion with Flandre: Hide and seek.

TH_ScarletSisters.jpg
Now it’s Remilia’s turn to hide and Flandre must to find her sister. To reduce the difficulty of the game, Remilia have told Flandre that there are N hiding places. By the way, there are exactly N-1 roads that connect all places, and each road has a length L. The roads are all bidirectional.

Although Flandre doesn’t tell her sister, she has a secret ability that she can build a new road between two places with a length X. But it’s no obvious that what’s the best plan to build the road, so Flandre asked you to calculate the sum of the decreasing distances from all places (including A and B) to A and B if building a road between A and B with length X, and then she can know how to find her sister as soon as possible.

Input

There are multiple test cases. For each test case:

The first line contains an integer N (1 <= N <= 50000) indicates the N hiding places in Scarlet Devil Mansion. Then followed by N-1 lines, each line contains three integers: Xi, Yi and Li (1 <= Xi,Yi <= N, 0 <= Li <= 10000), which means there’s a road between Xi and Yi, and the length is Li.

Next, there is one line contains an integer M (0 <= M <= 100000) indicates the M queries that Flandre asks. Then followed by M lines, each line contains three integers: Xi, Yi and Li (1 <= Xi,Yi <= N, 0 <= Li <= 10000), which means Flandre tries to build a new road between place Xi and Yi, and the length is Li.

Output

For each test case, output M integers corresponding to the Flandre’s queries.

Sample Input

5
1 2 3
1 3 2
2 4 1
3 5 3
2
4 5 1
1 2 2

Sample Output

24
5

Hint

In the first sample, if Flandre builds a road between 4 and 5 and the length is 1, the changes are: [4,5] 9->1 [2,5] 8->2 [3,4] 6->4 [5,4] 9->1 ([from, to] original distance -> nowdistance), so the answer is 8+6+2+8=24.

Author: DAI, Longao


作为车万众当然会挑车万题面的题目做了~
话说手推这题会获得一种蜜汁成就感……
可能是因为咱太弱推了2h+的原因……


思路:
这种题好像也不怎么能用除了 LCT L C T 以外的方法做吧……
采用将边拆为不计入 siz s i z ,但有点权的节点方式,对原树建立一棵 LCT L C T

令被询问的两个点为 s s t
现在假设咱们仅考虑所有点到 s s 的距离的变化情况。

首先把加入的边的两个端点旋到同一条链上(makeroot(s) access(t) a c c e s s ( t ) )
画一张图描述此时的情况:

pic1

其中,所有连接了链上的点和子树的边均为虚边。

可以发现一个性质,即链上每个点 u u 的虚儿子子树V内的点,由于 V V 中的点一定要经过u才能到达 s s ,因此V中节点的贡献与 u u 相同,可以一起计算。
于是只需要考虑链上每个点到s的距离的变化了~

考虑如何计算这个变化。
考虑找到这样一个点 p p ,满足p p p s路径上的点到 s s 的最短路在加入新边后不改变,而p t t 路径上除p外的所有点到 s s 的最短路改变。
可以发现,寻找这样的p只需要在被考虑的链构成的 splay s p l a y 上直接查找即可。具体见 findp() f i n d p ( ) 函数。

然后,由于上面的点最短路不变,考虑 p p 下方的每个点带来的最短路变化量。
比如假设有p1p2两个点在 p p t之间,它们带来的最短路变化分别为:
dis(p1)=dis(p1,s)dis(p1,t)li △ d i s ( p 1 ) = d i s ( p 1 , s ) − d i s ( p 1 , t ) − l i
dis(p2)=dis(p2,s)dis(p2,t)li △ d i s ( p 2 ) = d i s ( p 2 , s ) − d i s ( p 2 , t ) − l i
转化一下:
dis(p1)=dis(s,t)2dis(p1,t)li △ d i s ( p 1 ) = d i s ( s , t ) − 2 ∗ d i s ( p 1 , t ) − l i
dis(p2)=dis(s,t)2dis(p2,t)li △ d i s ( p 2 ) = d i s ( s , t ) − 2 ∗ d i s ( p 2 , t ) − l i

可以发现, dis(s,t)li ∑ d i s ( s , t ) − l i 可以很轻松地维护,只需支持维护 LCT L C T 上子树点权和和查询 p p 点下方的点个数即可。

现在的问题是,如何维护dis(x,t),即支持询问从某个点开始,其下方的点到 t t 的距离之和。
考虑到换根操作,需要同时维护dis(x,s),以便在下传 rev r e v 标记时方便地维护。
经过推导,可以得到形如这样的一个 update u p d a t e 函数:

    inline void update(int x)
    {
        push(x);push(ch[x][0]);push(ch[x][1]);
        siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+vsiz[x];
        sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+val[x];
        lsum[x]=lsum[ch[x][0]]+lsum[ch[x][1]]+(sum[ch[x][0]]+val[x])*siz[ch[x][1]]+vsiz[x]*sum[ch[x][0]];
        rsum[x]=rsum[ch[x][0]]+rsum[ch[x][1]]+(sum[ch[x][1]]+val[x])*siz[ch[x][0]]+vsiz[x]*sum[ch[x][1]];
    }

其中, siz[x] s i z [ x ] 是子树(含虚儿子)大小, vsiz[x] v s i z [ x ] 是单点虚儿子大小, sum[x] s u m [ x ] 是子树(不含虚儿子)点权和, val[x] v a l [ x ] 是点权(原树上的点点权为 0 0 ,只有代表一条边的节点才存在点权)。
注意上述的siz vsiz v s i z 中,代表边的节点是不计入贡献的。
同时若一个节点在原树上为节点,则在上述代码中通过将其 vsiz v s i z 初始值赋为 1 1 来计算其对子树大小的贡献。

可以发现,这个lsum rsum r s u m 维护的是从某个点开始,往上/下的所有点到 t t /s的距离之和。
具体推导过程……反正咱是拼凑了半天(30min+)推出来的……
然后剩下的就是很普通的 LCT L C T 操作辣~

下面放代码:
(话说ZOJ评测机是不是变慢了,网站显示跑得比咱快的dalao xzk(见友链)的程序在本机比咱慢了 13 1 3 ,开不开 O2 O 2 都一样)

#include<cstdio>
#include<cstring>
using namespace std;

typedef long long ll;
const int N=200009;

inline ll read()
{
    ll x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

inline void writes(ll x)
{
    if(x>=10)writes(x/10);
    putchar('0'+x%10);
}

inline void swap(ll &a,ll &b){a^=b;b^=a;a^=b;}
inline void swap(int &a,int &b){a^=b;b^=a;a^=b;}

inline void write(ll x,char ch='\n')
{
    if(x<0)putchar('-'),x=-x;
    writes(x);putchar(ch);
}

int n,m;
int to[N<<1],nxt[N<<1],beg[N],etot;

inline void add(int u,int v)
{
    to[++etot]=v;
    nxt[etot]=beg[u];
    beg[u]=etot;

}

namespace Flandre
{
    const int M=100009;
    int ch[M][2],fa[M],siz[M];
    int vsiz[M],stk[M],top,tot;
    ll val[M],sum[M],lsum[M],rsum[M];
    bool rev[M];

    inline void init()
    {
        etot=tot=0;
        memset(fa,0,sizeof(fa));
        memset(ch,0,sizeof(ch));
        memset(rev,0,sizeof(rev));
        memset(beg,0,sizeof(beg));
        memset(val,0,sizeof(val));
        memset(sum,0,sizeof(sum));
        memset(siz,0,sizeof(siz));
        memset(lsum,0,sizeof(lsum));
        memset(rsum,0,sizeof(rsum));
        memset(vsiz,0,sizeof(vsiz));
    }

    inline bool isroot(int x){return ch[fa[x]][0]!=x && ch[fa[x]][1]!=x;}

    inline void push(int x)
    {
        if(rev[x])
        {
            rev[ch[x][0]]^=1;
            rev[ch[x][1]]^=1;
            swap(ch[x][0],ch[x][1]);
            swap(lsum[x],rsum[x]);
            rev[x]=0;
        }
    }

    inline void push_down(int x)
    {
        stk[top=1]=x;
        for(int i=x;!isroot(i);i=fa[i])
            stk[++top]=fa[i];
        while(top)push(stk[top--]);
    }

    inline void update(int x)
    {
        push(x);push(ch[x][0]);push(ch[x][1]);
        siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+vsiz[x];
        sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+val[x];
        lsum[x]=lsum[ch[x][0]]+lsum[ch[x][1]]+(sum[ch[x][0]]+val[x])*siz[ch[x][1]]+vsiz[x]*sum[ch[x][0]];
        rsum[x]=rsum[ch[x][0]]+rsum[ch[x][1]]+(sum[ch[x][1]]+val[x])*siz[ch[x][0]]+vsiz[x]*sum[ch[x][1]];
    }

    inline void rotate(int x)
    {
        int y=fa[x],z=fa[y];
        int l=(ch[y][1]==x);
        if(!isroot(y))ch[z][ch[z][1]==y]=x;
        fa[x]=z;fa[y]=x;fa[ch[x][l^1]]=y;
        ch[y][l]=ch[x][l^1];ch[x][l^1]=y;
        update(y);update(x);if(z)update(z);
    }

    inline void splay(int x)
    {
        push_down(x);
        for(int y=fa[x],z=fa[y];!isroot(x);rotate(x),y=fa[x],z=fa[y])
            if(!isroot(y))
            {
                if(ch[y][1]==x^ch[z][1]==y)rotate(y);
                else rotate(x);
            }
    }

    inline void access(int x)
    {
        for(int t=0;x;t=x,x=fa[x])
        {
            splay(x);
            vsiz[x]+=siz[ch[x][1]];
            ch[x][1]=t;
            vsiz[x]-=siz[ch[x][1]];
            update(x);
        }
    }

    inline void makeroot(int x)
    {
        access(x);splay(x);rev[x]^=1;
    }

    inline int findp(int root,ll v)
    {
        int ret=0,now=root;
        while(now)
        {
            push(now);
            if(((val[now]+sum[ch[now][0]])<<1)<=sum[root]+v)
                ret=now,v-=((val[now]+sum[ch[now][0]])<<1),now=ch[now][1];
            else
                now=ch[now][0];
        }
        return ret;
    }

    inline ll query(int x,int y,ll v)
    {
        if(x==y)return 0;
        makeroot(x);access(y);splay(y);
        if(sum[y]<=v)return 0;
        int p=findp(y,v);
        if(!p)return 0;splay(p);
        return siz[ch[p][1]]*(sum[p]-v)-(rsum[ch[p][1]]<<1);
    }
}

using namespace Flandre;

inline void dfs(int u)
{
    siz[u]=((u>n)?0:1);
    for(int i=beg[u];i;i=nxt[i])
        if(to[i]!=fa[u])
        {
            fa[to[i]]=u;
            dfs(to[i]);
            siz[u]+=siz[to[i]];
        }
    vsiz[u]=siz[u];
}

inline void mina()
{
    init();
    for(int i=1,u,v,c;i<n;i++)
    {
        u=read();v=read();c=read();
        add(u,n+i);add(n+i,v);
        add(v,n+i);add(n+i,u);
        val[n+i]=sum[n+i]=c;
    }
    dfs(1);

    m=read();
    while(m--)
    {
        int x=read(),y=read(),l=read();
        write(query(x,y,l)+query(y,x,l));
    }
}

int main()
{
    while(scanf("%d",&n)!=EOF)
        mina();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值