题解 P4114 【Qtree1】

· · 题解

这算是一道树链剖分模板题,但是从维护点权改成了维护边权

宣传一波博客

如果不会树链剖分的话请先学习树链剖分

题解正式开始

这道题的一个难点就是如何维护边权,那肯定要将将边权转化成点权才行。

我们考虑一条边,它一定连接父亲节点和儿子节点(这不肯定的吗……),如果用父亲节点来存的话,该节点的其他儿子的边就没法存,但如果用儿子节点来存的话就没有这种问题(一个节点可以有多个儿子,但不会有超过一个的父亲),这样,将边权值存在哪的问题就解决了。

那怎么存呢?我们考虑一下,存点权的树链剖分是在第二遍DFS的时候处理点权的,因此,我们可以记录从当前节点到它的父节点的边权作为它的点权。

第二遍DFS的代码如下:

inline void did2(long long u,long long topf,long long l1)//l1代表该节点到父亲节点的距离。
{
    id[u]=++cnt;
    w[cnt]=l1;//这就是记录边权了
    top[u]=topf;
    if (!son[u]) return;
    long long lx;//因为要先找重儿子,所以提前求出该节点到重儿子的距离
    for (long long i=he[u];i;i=ne[i]) if (to[i]==son[u]) lx=ll[i];
    did2(son[u],topf,lx);
    for (register long long i=he[u];i;i=ne[i])
    {
        register long long v=to[i];
        if (v==f[u]||v==son[u]) continue;
        did2(v,v,ll[i]);//ll数组记录路径长,这就是儿子节点到该节点的距离了
    }
}

不要以为这就完事了,当我们要求值时,原本的代码如下:

inline long long quimax(long long x,long long y)
{
    register long long ans=0;
    while(top[x]!=top[y])
    {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        ans=(ans+qui(1,id[top[x]],id[x]));
        x=f[top[x]];
    }
    if (dep[x]>dep[y]) swap(x,y);
    ans=(ans+qui(1,id[x],id[y]));
    return ans;
}

有没有发现有什么问题?

我们发现,最后这一段

    ans=(ans+qui(1,id[x],id[y]));

中,我们求的其实是f[x]到y的路径的边权和,而不是x到y的路径的边权和,我们考虑到x,y在一条重链上,所以求son[x],y就相当于求x,y的值了。

正确代码如下:

inline long long quimax(long long x,long long y)
{
    register long long ans=-1e11;
    while(top[x]!=top[y])
    {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        ans=max(ans,qui(1,id[top[x]],id[x]));
        x=f[top[x]];
    }
    if (dep[x]>dep[y]) swap(x,y);
    ans=max(ans,qui(1,id[son[x]],id[y]));//求x到y的路径的边权和。
    return ans;
}

这道题还有一个问题就是如何找出存储第x条边的节点

这就要利用链式前向星的一点性质了

对于一个有向图,第x条边所指向的节点就是to[x](bian[x].to),而对于无向图的话,第x条边所连接的节点就是to[2 x]和to[2 x-1] (bian[2 s].to和bian[2 x-1].to,这个依据个人习惯),而这两个节点中深的那个节点就是儿子节点了(因为深度[儿子]=深度[父亲]+1啊……),然后修改这个节点就行了……

其他的就和树链剖分的代码一样了……总代码如下,和模板树链剖分一样的和上文解释过的,就不加注释了……(累死了)

#include<bits/stdc++.h>
using namespace std;
long long qq,n,m,r1,p,q,i,k,x,y,cnt,top[1000001],w[1000001],a[1000001],dep[1000001],f[1000001],siz[1000001],he[1000001],ll[2000001],ne[2000001],to[2000001],z[4000001],l[4000001],r[4000001],c[4000001],son[1000001],id[1000001];
inline void did1(long long u,long long fat,long long dee)
{
    dep[u]=dee;
    f[u]=fat;
    siz[u]=1;
    register long long max1=-1;
    register long long v;
    for (register long long i=he[u];i;i=ne[i])
    {
        v=to[i];
        if (v==fat) continue;
        did1(v,u,dee+1);
        if (siz[v]>max1)
        {
            son[u]=v;
            max1=siz[v];
        }
        siz[u]+=siz[v];
    }
}
inline void did2(long long u,long long topf,long long l1)
{
    id[u]=++cnt;
    w[cnt]=l1;
    top[u]=topf;
    if (!son[u]) return;
    long long lx;
    for (long long i=he[u];i;i=ne[i]) if (to[i]==son[u]) lx=ll[i];
    did2(son[u],topf,lx);
    for (register long long i=he[u];i;i=ne[i])
    {
        register long long v=to[i];
        if (v==f[u]||v==son[u]) continue;
        did2(v,v,ll[i]);
    }
}
inline void build(long long u,long long l1,long long r1)
{
  l[u]=l1;
  r[u]=r1;
  if (l1==r1)
  {
    z[u]=w[l1];
    return;
  }
  build(u*2,l1,(l1+r1)/2);
  build(u*2+1,(l1+r1)/2+1,r1);
  z[u]=max(z[u*2],z[u*2+1]);
}
inline void jia(long long u,long long l1,long long r1,long long k)
{
  if ((l[u]>r1)||(r[u]<l1)) return;
  if ((l[u]>=l1)&&(r[u]<=r1))
  {
    z[u]=k;
    return;
  }
  jia(u*2,l1,r1,k);
  jia(u*2+1,l1,r1,k);
  z[u]=max(z[u*2],z[u*2+1]);
}
inline long long qui(long long u,long long l1,long long r1)
{
  if ((l1>r[u])||(r1<l[u])) return -1e11;
  if ((l1<=l[u])&&(r1>=r[u])) return z[u];
    return max(qui(u*2,l1,r1),qui(u*2+1,l1,r1));
}
inline long long quimax(long long x,long long y)
{
    register long long ans=-1e11;
    while(top[x]!=top[y])
    {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        ans=max(ans,qui(1,id[top[x]],id[x]));
        x=f[top[x]];
    }
    if (dep[x]>dep[y]) swap(x,y);
    ans=max(ans,qui(1,id[son[x]],id[y]));
    return ans;
}
int main()
{
    scanf("%lld",&n);
    for (i=1;i<n;i++)
    {
        scanf("%lld%lld%lld",&x,&y,&r1);//链式前向星
        cnt++;
        ne[cnt]=he[x];
        to[cnt]=y;
        ll[cnt]=r1;
        he[x]=cnt;
        cnt++;
        ne[cnt]=he[y];
        to[cnt]=x;
        ll[cnt]=r1;
        he[y]=cnt;
    }
    did1(1,0,1);
    cnt=0;
    did2(1,1,0);//个人习惯1节点到0节点距离为0……实际上啥都可以。
    build(1,1,n);
    q=getchar();
    while(q<'A'||q>'Z') q=getchar();
    qq=getchar();
    while (qq>='A'&&qq<='Z') qq=getchar();//千万不要用cin,太慢了,作者喜欢用getchar();
    while(q!='D')
    {
        scanf("%lld%lld",&x,&y);
        if (q=='Q')
            if (x!=y)
                printf("%lld\n",quimax(x,y));
            else
                printf("0\n");
        else
        {
            r1=to[2*x];
            p=to[2*x-1];//找出这条边所连的两个节点
            if (dep[r1]>dep[p]) jia(1,id[r1],id[r1],y);
            else jia(1,id[p],id[p],y);//找出儿子节点,进行修改
        }
        q=getchar();
        while(q<'A'||q>'Z') q=getchar();
        qq=getchar();
        while(qq>='A'&&qq<='Z') qq=getchar();
    }
    return 0;
}

这道题就完事了