树链剖分学习笔记 && SPOJ QTREE

昨天晚上脑子一抽,冒出来一个想法:“我要学树剖!”然后,今天下午+晚上就砸给了树剖….讲真,NOIP之前学省选内容并不好…我这是在作死…..——前言。

树链剖分,计算机术语,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、SBT、SPLAY、线段树等)来维护每一条链。(来自度娘百科)

一般是用线段树来维护吧….看上去代码很长,其实主要部分是线段树。好像线段树也是省选内容呢= =,mdzz反正我们LOI基本上都会。

怎么把这棵树剖开呢,我们需要两个DFS。

第一个DFS处理出每个点的深度deep,爹fa,以该点为根的子树大小siz,和每个点的重儿子,我们规定对于一个点t,他的儿子中siz最大的是他的重儿子

重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
轻儿子:v的其它子节点。
重边:点v与其重儿子的连边。
轻边:点v与其轻儿子的连边。
重链:由重边连成的路径。
轻链:轻边。

丢一点概念。

在第二个DFS里,主要是搞出top数组,top[i]为i所在的这条链的最顶端的元素,
我们还需要一个数组xds记录树上的每条边(或者点,视题意而定)在线段树中的位置。

1.对于节点v,当son[v]存在(即v不是叶子节点)时,显然有top[son[v]] = top[v]。线段树中,v的重边应当在v的父边的后面,记xds[son[v]] = totw+1,totw表示最后加入的一条边在线段树中的位置。此时,为了使一条重链各边在线段树中连续分布,应当进行dfs_2(son[v]);
⒉对于v的各个轻儿子u,显然有top[u] = u,并且xds[u] = totw+1,进行dfs_2过程。这就求出了top和w。(搬运)

我知道你们看不懂我在说什么….戳这里—>hahah

那接下来就是例题了。

SPOJ QTREE
戳我
题目大意:就是一个裸的树剖
输入:输入一个t,代表有t组数据
对于每组数据,第一行有个n<=10000代表有多少个点,
接下来n-1行,每行有3个数a,b,c代表从a到b的这条边权值为c
接下来每行输入一个字符串和两个整数a,b,
分别对应操作,
如果字符串是DONE就结束
字符串是CHANGE就把边a的权值改为b
字符串是QUERY就询问从a点到b点路径上的最大权值。
输出:对于每个QUERY对应一个输出

这个题是维护边的,我们需要把每一条边的权值下放到连接它的两个点中深度较大的点(根节点是没有爹的)

我们在xds数组中记录的值,xds[i]就代表第i条边在xds中的位置了。(当然,这里的第一条边就是代表根节点和他父亲连的边,但是他没有父亲,所以第一条边并没有卵用)

在进入询问之前,我们要预处理一下,
我们需要把每一条边的权值下放到连接它的两个点中深度较大的点
所以,我们对于每一条边,连接它将深度较大的点在线段树中的值修改为这条边的权值。

只用线段树维护最大值,并且是单边修改,线段树的修改部分非常好写。只要找到这个点在线段树中的位置直接修改就行了。

这个题的关键地方,是在询问最值。
这里写图片描述
图中的较粗的边是重边,蓝色数字代表每条边在线段树中的位置。

看看这张图,再想想DFS2,每条重链上的边的序号一定是连续的

如果我们要询问x到y路径上的最值。

假设a是上面的11节点,b是上面的14节点。
那么令fax==top[a]==2,fay==top[10]==10;
这时,很明显deep[fax]< deep[fay],我们对fay进行操作显然更好。
所以将fax,fay。x,y分别swap
再求fax到x的最大距离。
因为x到fax,他们的边在线段树上是一段连续的区间,所以就等同于线段树区间最值查询。
然后更新x和fax,直到fax==fay,此时x和y一定在同一条链上了。

此时,如果deep[x]>deep[y],我们将他们swap一下。
因为他们在同一条链上,所以求一遍xds[x]+1到xds[y]的最值就好了

为什么是xds[x]+1呢?

之前也说了xds[x],代表他到他爹的这条边。
显然,在这里是用不到的。
至此结束。

代码贼TM长…..

#include<algorithm>
#include<cstring>
#include<cstdio>
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
const int maxn=50000;
struct Edge
{
    int to;
    int next;
}edge[maxn];
int tot; 
int head[maxn];
void add(int f,int t)
{
    edge[++tot]=(Edge){t,head[f]};
    head[f]=tot;
}
int deep[maxn],fa[maxn],siz[maxn],son[maxn];
void dfs1(int f,int t)
{
    deep[t]=deep[f]+1;
    fa[t]=f;
    siz[t]=1;
    for(int i=head[t];i;i=edge[i].next)//神TMt打成了f 
    {
        Edge e=edge[i];
        if(e.to==f)
            continue;
        dfs1(t,e.to);
        siz[t]+=siz[e.to];
        if(siz[e.to]>siz[son[t]])
            son[t]=e.to;
    }
}
int top[maxn],xds[maxn];
int ttot;
void dfs2(int u,int topu)
{
    top[u]=topu;
    xds[u]=++ttot;
    if(!son[u])
        return;
    dfs2(son[u],topu);
    for(int i=head[u];i;i=edge[i].next)
    {
        Edge e=edge[i];
        if(e.to==fa[u]||e.to==son[u])
            continue;
        dfs2(e.to,e.to);
    }
}
struct Tree
{
    int l,r;
    int maxx;
}t[maxn<<3];
void up(int p)
{
    t[p].maxx=max(t[p<<1].maxx,t[p<<1|1].maxx);
}
void expand(int p,int l,int r)
{
    t[p].l=l;
    t[p].r=r;
    if(t[p].l==t[p].r)
    {
        t[p].maxx=0;
        return;
    }
    int mid=(l+r)>>1;
    expand(p<<1,l,mid);
    expand(p<<1|1,mid+1,r);
    up(p);
}
void change(int p,int pos,int v)
{
    if(t[p].l==t[p].r)
    {
        t[p].maxx=v;
        return;
    }
    int mid=(t[p].l+t[p].r)>>1;
    if(pos<=mid)
        change(p<<1,pos,v);
    else
        change(p<<1|1,pos,v);
    up(p);
}
int ask_max(int p,int l,int r)
{
    int ans=0;
    if(l<=t[p].l&&t[p].r<=r)
    {
        return t[p].maxx;
    }
    int mid=(t[p].r+t[p].l)>>1;
    if(l<=mid)
        ans=max(ans,ask_max(p<<1,l,r));
    if(mid+1<=r)
        ans=max(ans,ask_max(p<<1|1,l,r));
    return ans;
}
int find(int x,int y)
{
    int fax=top[x];
    int fay=top[y];
    int ans=0;
    while(fax!=fay)
    {
        if(deep[fax]<deep[fay])
        {
            swap(fax,fay);
            swap(x,y);
        }
        ans=max(ans,ask_max(1,xds[fax],xds[x]));
        x=fa[fax];//这一行打成了x=fax 
        fax=top[x];
    }
    if(deep[x]>deep[y])
    swap(x,y);
    if(x!=y)
    {
        ans=max(ans,ask_max(1,xds[x]+1,xds[y]));
    }
    return ans;

}
int ff[maxn],tt[maxn],dd[maxn];
void clr()
{
    mem(ff);
    mem(tt);
    mem(dd);
    mem(edge);
    mem(top);
    mem(xds);
    mem(t);
    mem(fa);
    mem(son);
    mem(head);
    mem(deep);
    mem(siz);
    tot=0;
    ttot=0; 
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        clr();
        int n;
        scanf("%d",&n);
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&ff[i],&tt[i],&dd[i]);
            add(ff[i],tt[i]);
            add(tt[i],ff[i]);
        }
        dfs1(0,1);
        dfs2(1,1);
        expand(1,1,n);
        char hah[233];
        for(int i=1;i<n;i++)
        {
            if(deep[ff[i]]>deep[tt[i]])
                swap(ff[i],tt[i]);
            change(1,xds[tt[i]],dd[i]);
        }
        while(1)
        {
            scanf("%s",hah);
            if(hah[0]=='D')
                break;
            else
            {
                int a,b;
                scanf("%d%d",&a,&b);
                if(hah[0]=='C')
                    change(1,xds[tt[a]],b);
                else
                    printf("%d\n",find(a,b));
            }
        }
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值