[BZOJ3589]动态树(树链剖分+dfs序+lca)

23 篇文章 0 订阅
20 篇文章 0 订阅

题目描述

传送门

题解

修改的操作直接做就可以了。查询的时候问题主要出在会有重复的路径。我的思路就是将重复的路径砍断,变成不重复的,然后最后依次查询就可以了。由于k比较小, O(k2) 判断就是可以接受的。
由于树枝全部是从某个节点到根的路径的一段,处理起来就比较方便了。处理两条树枝的方法是:设两条树枝 (x1,y1)(x2,y2) ,且 h[x1]<h[y1],h[x2]<h[y2],h[x1]<h[x2] 。首先求出 r=lca(y1,y2) ,yy(为r的一个儿子,且满足y2在yy的子数中)。若 h[x1]>h[r] 或者 h[x2]>h[r] ,则说明两条树枝不相交,可以不作任何处理。否则的话,使y1=yy。注意判断空树枝的情况。
这种方法只会把一条树枝砍得更短,而不会变长,所以不用担心和原先判断过的冲突。 k2 枚举一下就行了。

说一些写的时候出现的问题:
①做lca的时候某些时候需要将xy交换,但是交换了之后求出的两个儿子就对应不起来了。所以为了方便起见,按照y的深度排了一下序。
②这道题让我更深入地了解了lca。其实lca刚开始的时候一直向上蹦,它的原理是任意一个数都可以用二进制分解为至多logn个数相加,并且这样的话保证了x最后蹦到和y相同的高度。但是如果这样直接记录儿子的话是不行的,因为有可能最后蹦的不是一步。解决方法是加一个限制条件,y在蹦的过程中不能直接蹦到x的高度,但是最后要一步一步蹦上去。注意最后一定要让y蹦到和x相同的高度,否则后面的处理会有问题。

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define N 200005
#define sz 18
#define LL long long

const LL Mod=2147483648LL;
int n,q,x,y,opt,dfs_clock,pt,add,k;
int tot,point[N],nxt[N*2],v[N*2];
int father[N],size[N],son[N],h[N],top[N],in[N],out[N],f[N][sz+5],mi[sz+5];
LL sum[N*4],delta[N*4],ans;
struct hp{int x,y;}edge[10];
struct hq{int fa,xx,yy;};

void addedge(int x,int y)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void dfs_1(int x,int fa,int dep)
{
    size[x]=1; h[x]=dep; father[x]=fa;
    for (int i=1;i<sz;++i)
    {
        if (h[x]-mi[i]<1) break;
        f[x][i]=f[f[x][i-1]][i-1];
    }
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa)
        {
            f[v[i]][0]=x;
            dfs_1(v[i],x,dep+1);
            size[x]+=size[v[i]];
            if (size[son[x]]<size[v[i]]) son[x]=v[i];
        }
}
void dfs_2(int x,int fa)
{
    if (son[fa]!=x) top[x]=x;
    else top[x]=top[fa];
    in[x]=++dfs_clock;
    if (son[x]) dfs_2(son[x],x);
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa&&v[i]!=son[x])
            dfs_2(v[i],x);
    out[x]=dfs_clock;
}
hq lca(int x,int y)
{
    hq ans;
    int xx=x,yy=y;
    for (int i=sz-1;i>=0;--i)
        while (h[f[x][i]]>=h[y]&&f[x][i]!=y)
            xx=x,x=f[x][i];
    while (h[f[x][0]]>=h[y]) xx=x,x=f[x][0];
    if (x==y)
        return ans=(hq){x,xx,yy};
    for (int i=sz-1;i>=0;--i)
        if (f[x][i]!=f[y][i])
            yy=y,xx=x,x=f[x][i],y=f[y][i];
    return ans=(hq){f[x][0],x,y};
}

void update(int now)
{
    sum[now]=(sum[now<<1]+sum[now<<1|1])%Mod;
}
void pushdown(int now,int l,int r,int mid)
{
    if (delta[now])
    {
        sum[now<<1]=(sum[now<<1]+delta[now]*(mid-l+1))%Mod;
        sum[now<<1|1]=(sum[now<<1|1]+delta[now]*(r-mid))%Mod;
        delta[now<<1]=(delta[now<<1]+delta[now])%Mod;
        delta[now<<1|1]=(delta[now<<1|1]+delta[now])%Mod;
        delta[now]=0;
    }
}
void interval_change(int now,int l,int r,int lrange,int rrange,LL v)
{
    int mid=(l+r)>>1;
    if (lrange<=l&&r<=rrange)
    {
        sum[now]=(sum[now]+v*(r-l+1)%Mod)%Mod;
        delta[now]=(delta[now]+v)%Mod;
        return;
    }
    pushdown(now,l,r,mid);
    if (lrange<=mid) interval_change(now<<1,l,mid,lrange,rrange,v);
    if (mid+1<=rrange) interval_change(now<<1|1,mid+1,r,lrange,rrange,v);
    update(now);
}
LL query(int now,int l,int r,int lrange,int rrange)
{
    int mid=(l+r)>>1; LL ans=0;
    if (lrange<=l&&r<=rrange) return sum[now];
    pushdown(now,l,r,mid);
    if (lrange<=mid) ans=(ans+query(now<<1,l,mid,lrange,rrange))%Mod;
    if (mid+1<=rrange) ans=(ans+query(now<<1|1,mid+1,r,lrange,rrange))%Mod;
    return ans;
}

LL ask(int u,int t)
{
    LL ans=0;
    int f1=top[u],f2=top[t];
    while (f1!=f2)
    {
        if (h[f1]<h[f2])
        {
            swap(f1,f2);
            swap(u,t);
        }
        ans=(ans+query(1,1,n,in[f1],in[u]))%Mod;
        u=father[f1];
        f1=top[u];
    }
    if (in[u]>in[t]) swap(u,t);
    ans=(ans+query(1,1,n,in[u],in[t]))%Mod;
    return ans;
}

int cmp(hp a,hp b)
{
    return h[a.y]>h[b.y];
}

int main()
{
    mi[0]=1; for (int i=1;i<=sz;++i) mi[i]=mi[i-1]*2;
    scanf("%d",&n);
    for (int i=1;i<n;++i)
    {
        scanf("%d%d",&x,&y);
        addedge(x,y);
    }
    dfs_1(1,0,1);
    dfs_2(1,0);
    scanf("%d",&q);
    for (int i=1;i<=q;++i)
    {
        scanf("%d",&opt);
        if (opt==0)
        {
            scanf("%d%lld",&pt,&add);
            interval_change(1,1,n,in[pt],out[pt],add);
        }
        else
        {
            scanf("%d",&k);
            for (int i=1;i<=k;++i)
            {
                scanf("%d%d",&edge[i].x,&edge[i].y);
                if (h[edge[i].x]>h[edge[i].y]) swap(edge[i].x,edge[i].y);
            }
            sort(edge+1,edge+k+1,cmp);
            for (int i=1;i<k;++i)
                for (int j=i+1;j<=k;++j)
                {
                    int x1=edge[i].x,y1=edge[i].y,x2=edge[j].x,y2=edge[j].y;
                    if (!x1||!x2||!y1||!y2) continue;
                    hq r=lca(y1,y2);
                    if (h[x1]>h[r.fa]||h[x2]>h[r.fa]) continue;

                    if (h[x1]<h[x2])
                    {
                        if (r.yy==r.fa) edge[j].x=edge[j].y=0;
                        else edge[j].x=r.yy;
                    }
                    else
                    {
                        if (r.xx==r.fa) edge[i].x=edge[i].y=0;
                        else edge[i].x=r.xx;
                    }
                }
            ans=0;
            for (int i=1;i<=k;++i)
                if (edge[i].x&&edge[i].y)
                    ans=(ans+ask(edge[i].x,edge[i].y))%Mod;
            printf("%lld\n",ans);
        }
    }
}

总结

①lca等基础算法掌握不扎实。不是不会写,而是对原理认识的不是很清楚。
②考虑问题不全面。刚开始的树枝还会加长。以后一定要想明白,判断清楚没有可能的错误再写。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值