【bzoj3589】动态树 树链剖分+线段树

题目大意:
给定一棵树,支持以下两个操作:
0、子树修改
1、查询几条的链并的权值和,答案模2^31。
(这些链为某个节点到根的路径的一部分)

题目分析:(树链剖分+线段树)
首先,这道题跟动态树没什么关系。
其他的题解有用什么容斥原理,还有奇怪的动态树做的,我觉得树链剖分+线段树就足够了。

我们假设不是求这些链的并,而是求这些链的权值和。
那么只需要树链剖分之后维护线段树就可以了。

那么剩下的问题就是如何求这些链的并。

无交的链自然不用处理。
由于链的奇怪性质,有交的链必然是类似于下图的样子:
这里写图片描述
这样的链在两个最低点的lca处以上的部分相当于公共部分,所以我们完全可以转化成两个这样的链:
这里写图片描述

就是把有并的两条链拆成覆盖原来两条链的所有点的两条不想交的链。

这个我们可以枚举两条链来拆。
具体可以这样做:
求两个链底的LCA
如果这个LCA的深度低于任意一个链顶,则证明这两条链没有交。
否就把一个条链的链顶设为两个链顶中深度较浅的那个,另一条链的链顶设为LCA的下一个(详见代码)。

注意:
在枚举两条链的时候,我们相当于往原来的链的集合中新加一个,在与其他链进行合并的时候,要使当前新加进来的链能包含所有新加进来的点,所以要每次把原来的链与LCA断开。

代码如下:

#include <cstdio>
#include <algorithm>
#include <iostream>
#define N 200001
#define ls(c) (c<<1)
#define rs(c) (c<<1|1)
using namespace std;
struct segment{
    int l,r;
    int sum,mark;
}seg[N<<2];
int n,m,opt,k,x,y;
int a[8],b[8];
int fir[N],nes[N<<1],v[N<<1],tot=1;
int fa[N],dep[N],zon[N],sz[N],pos[N],ld[N],top;
void edge(int x,int y)
{
    v[++tot]=y;
    nes[tot]=fir[x];
    fir[x]=tot;
    return;
}
#define edge(x,y) edge(x,y),edge(y,x)
void dfs1(int c)
{
    sz[c]=1;
    dep[c]=dep[fa[c]]+1;
    for(int t=fir[c];t;t=nes[t])
    {
        if(v[t]==fa[c]) continue;
        fa[v[t]]=c;
        dfs1(v[t]);
        sz[c]+=sz[v[t]];
        if(sz[v[t]]>sz[zon[c]]) zon[c]=v[t];
    }
}
void dfs2(int c)
{
    pos[c]=++top;
    ld[c]=c;
    if(zon[fa[c]]==c) ld[c]=ld[fa[c]];
    if(zon[c]) dfs2(zon[c]);
    for(int t=fir[c];t;t=nes[t])
    {
        if(v[t]==fa[c] || v[t]==zon[c]) continue;
        dfs2(v[t]);
    }
}
int lca(int x,int y)
{
    while(ld[x]!=ld[y])
    {
        if(dep[ld[x]]<dep[ld[y]]) swap(x,y);
        x=fa[ld[x]];
    }
    if(dep[x]<dep[y]) swap(x,y);
    return y;
}
void add_mark(int c,int v)
{
    seg[c].sum+=(seg[c].r-seg[c].l+1)*v;
    seg[c].mark+=v;
}
void push_down(int c)
{
    if(seg[c].l!=seg[c].r)
    {
        add_mark(ls(c),seg[c].mark);
        add_mark(rs(c),seg[c].mark);
    }
    seg[c].mark=0;
}
void update(int c,int l,int r,int y)
{
    if(l<=seg[c].l && r>=seg[c].r)
    {
        add_mark(c,y);
        return;
    }
    push_down(c);
    int mid=seg[c].l+seg[c].r>>1;
    if(l<=mid) update(ls(c),l,r,y);
    if(r>mid)  update(rs(c),l,r,y);
    seg[c].sum=seg[ls(c)].sum+seg[rs(c)].sum;
}
int query(int c,int l,int r)
{
    push_down(c);
    if(l<=seg[c].l && r>=seg[c].r) return seg[c].sum;
    int mid=seg[c].l+seg[c].r>>1;
    if(r<=mid) return query(ls(c),l,r);
    if(l>mid)  return query(rs(c),l,r);
    return query(ls(c),l,r)+query(rs(c),l,r);
}
void build_tree(int c,int l,int r)
{
    seg[c].l=l,seg[c].r=r,seg[c].sum=seg[c].mark=0;
    if(l==r) return;
    int mid=l+r>>1;
    build_tree(ls(c),l,mid);
    build_tree(rs(c),mid+1,r);
}
void query()
{
    int ans=0;
    for(int i=1;i<=k;i++)
    {
        if(dep[a[i]]>dep[b[i]]) swap(a[i],b[i]);
        for(int j=1;j<i;j++)
        {
            if(a[j]==0 || b[j]==0) continue;
            int LCA=lca(b[i],b[j]);
            if(dep[LCA]<dep[a[j]] || dep[LCA]<dep[a[i]]) continue;
            a[i]=dep[a[i]]<dep[a[j]]?a[i]:a[j];
            if(b[j]==LCA) {a[j]=b[j]=0;continue;}
            a[j]=b[j];
            while(ld[a[j]]!=ld[LCA])
            {
                if(fa[ld[a[j]]]==LCA)
                {
                    a[j]=ld[a[j]];
                    break;
                }
                a[j]=fa[ld[a[j]]];
            }
            if(ld[a[j]]==ld[LCA]) a[j]=zon[LCA];
        }
    }
    for(int i=1;i<=k;i++)
    {
        if(a[i]==0 || b[i]==0) continue;
        while(ld[a[i]]!=ld[b[i]])
        {
            ans+=query(1,pos[ld[b[i]]],pos[b[i]]);
            b[i]=fa[ld[b[i]]];
        }
        ans+=query(1,pos[a[i]],pos[b[i]]);
    }
    printf("%d\n",ans&(0x7fffffff));
}
int main()
{
    scanf("%d",&n);
    for(int i=1,x,y;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        edge(x,y);
    }
    dfs1(1);dfs2(1);
    build_tree(1,1,n);
    scanf("%d",&m);
    while(m--)
    {
        scanf("%d",&opt);
        switch(opt)
        {
            case 0:
                scanf("%d%d",&x,&y);
                update(1,pos[x],pos[x]+sz[x]-1,y);
                break;
            case 1:
                scanf("%d",&k);
                for(int i=1;i<=k;i++)
                    scanf("%d%d",&a[i],&b[i]);
                query();
                break;
        }
    }
    return 0;
}

附带一个小数据生成器:

#include <cstdio>
#include <ctime>
#include <algorithm>
#include <iostream>
#define N 120000
using namespace std;
int n,m;
int f[N];
int fa[N],dep[N];
int fir[N],nes[N<<1],v[N<<1],tot=1;
void edge(int x,int y)
{
    v[++tot]=y;
    nes[tot]=fir[x];
    fir[x]=tot;
    return;
}
#define edge(x,y) edge(x,y),edge(y,x)
int findie(int c)
{
    if(f[c]!=c) f[c]=findie(f[c]);
    return f[c];
}
void dfs(int c)
{
    dep[c]=dep[fa[c]]+1;
    for(int t=fir[c];t;t=nes[t])
    {
        if(fa[c]==v[t]) continue;
        fa[v[t]]=c;
        dfs(v[t]);
    }
}
int main()
{
    freopen("input.in","w",stdout);
    srand(time(0));
    n=10;
    m=20;
    cout<<n<<endl;
    for(int i=1;i<=n;i++) f[i]=i;
    for(int i=1,x,y;i<n;i++)
    {
        x=rand()%n+1;y=rand()%n+1;
        while(findie(x)==findie(y)) x=rand()%n+1,y=rand()%n+1;
        f[findie(x)]=findie(y);
        edge(x,y);
        cout<<x<<" "<<y<<endl;
    }
    dfs(1);
    cout<<m<<endl;
    for(int i=1,x,y,k,t;i<=m;i++)
    {
        int opt=rand()%2;
        switch(opt)
        {
        case 0:
            cout<<opt<<" "<<rand()%n+1<<" "<<rand()%10<<endl;
            break;
        case 1:
            k=rand()%5+1;
            cout<<opt<<" "<<k<<" ";
            for(int j=1;j<=k;j++)
            {
                x=rand()%n+1;
                t=rand()%dep[x];
                y=x;
                while(t--) y=fa[y];
                cout<<x<<" "<<y<<" ";
            }
            cout<<endl;
            break;
        }
    }

    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值