【JZOJ4005】【GDKOI2015】树(树链剖分)

Problem

这里写图片描述

Input

这里写图片描述

Hint

这里写图片描述

Solution

  (首先义正辞严地指摘一下出数据的人,竟然出这么水的数据)这道题一眼链剖,对吧。但是反转操作不太好搞。于是我就打了个暴力,(本来也可以满分)但是打错了,样例都过不去,就没交。
  正解也即链剖。但是由于要维护子树信息的反转操作,我们必须以一种比较奇特的方式建树。
  首先我们发现数据很小,所以递归dfs也不会爆栈。那么我们就愉快地dfs一遍,求出每个点的重儿子;然后再愉快地dfs一遍,按照链剖的套路,按重儿子优先的顺序给每个点重标号,然后求出每个重标号后的点的深度、父亲节点、重链链顶。
  但是由于要维护子树信息,我们还必须求出每个重标号后的点x的子树中标号最大的点tail[x]。这样,由于每个点的新标号肯定比它的子节点小,而且定然是连续的,我们就可以确保x到tail[x]恰为x的子树。而且为了后面便于反转,我用了一种极不寻常的建树方式:我没有按照套路,将每条重链都建成一棵线段树;而是将一整棵树建成一棵线段树。这样一来,我反转时,一次修改便可修改到许多不同的重链上的信息。
  而且,由于本题有多种颜色,且每种颜色的信息不得混淆,我就为每种颜色各开一棵线段树(反正至多10种颜色),也即开10个root,记录一段区间内这种颜色的点的权值和。举个栗子,假若点3为0色,点4为2色,则2色的线段树中[3,4]这个区间就只加上点4的权值,而不加上点3的权值。
  这样的话,直接分治一遍不好建树,我就一个一个点地插进线段树中。
  预处理完了,我们就必须直面那些操作。
  对于反转操作(u,x,y),我们可以从root[x]和root[y]出发,从区间[1,n]搜区间[u,tail[u]](就像普通的线段树区间修改一样),每搜到一个完整的区间,我们就使这两个区间的编号交换。这就有两种情况:1.u为1,那么整棵树都会被反转,所以root被交换,那么就相当于整棵树被反转;2.非root节点被交换,那么其实就是它们的父亲节点的那个子节点交换了,即相当于以它们为根的子树被反转。
  对于询问操作,我们就像普通的链剖那样,只不过要注意对应的颜色。
  对于修改操作(u,c,val),我不能像普通的链剖那样修改,因为我的建树方式不支持我这样做。所以我是搜一遍每种颜色,然后各以 O(log2n) O ( l o g 2 n ) 的时间看一看在这种颜色的树中,[u,u]这段区间权值和v是否不为0:找到了则在那棵树中的u位置插一个权值-v;没找到则说明u那个点的权值本身即为0,不用管它。不管找没找到,我们都要在c颜色的那棵树中的u位置插一个权值val。
  时间复杂度: O((n+qlog2n)log2n) O ( ( n + q l o g 2 n ) l o g 2 n )

Code

#include <cstdio>
#include <algorithm>
using namespace std;
#define N 50001
#define M 2*N
#define S N*40
#define dx dfn[x]
#define dy dfn[y]
#define A(v) son[v][0]
#define B(v) son[v][1]
#define hu top[u]
#define hv top[v]
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fs for(int i=last[x];i;i=next[i])
int i,n,C[N],V[N],x,y,tot,tov[M],next[M],last[N],fat[N],size[N],ps[N],dfn[N],num[N],fa[N],
    deep[N],top[N],tail[N],root[10],f[S],son[S][2],m,u,v,c,val,ans;
char s[6];
inline void insert(int x,int y)
{
    tov[++tot]=y;
    next[tot]=last[x];
    last[x]=tot;
}
void dfs(int x)
{
    size[x]=1;
    int y;
    fs
    {
        y=tov[i];
        if(y==fat[x])continue;
        fat[y]=x;
        dfs(y);
        size[x]+=size[y];
        if(size[y]>size[ps[x]])ps[x]=y;
    }
}
void dfs1(int x)
{
    int y;
    if(y=ps[x])
    {
        num[dy=++tot]=y;
        deep[dy]=deep[dx]+1;
        top[dy]=top[dx];
        dfs1(y);
        fa[dy]=dx;
        tail[dx]=tail[dy];
    }
    else
    {
        tail[dx]=dx;
        return;
    }
    fs
    {
        y=tov[i];
        if(y==fat[x]||y==ps[x])continue;
        num[dy=++tot]=y;
        deep[dy]=deep[dx]+1;
        top[dy]=dy;
        dfs1(y);
        fa[dy]=dx;
        tail[dx]=tail[dy];
    }
}
inline int&R(int&x)
{
    return x=x?x:++tot;
}
void ins(int v,int l,int r,int x,int val)
{
    f[v]+=val;
    if(l==r)return;
    int mid=(l+r)/2;
    if(x<=mid)
            ins(R(A(v)),l,mid,x,val);
    else    ins(R(B(v)),mid+1,r,x,val);
}
void maketree()
{
    fo(i,1,n)ins(root[C[i]],1,n,dfn[i],V[i]);
}
void init()
{
    dfs(1);

    dfs1(dfn[1]=num[1]=top[1]=tot=1);

    tot=0;
    fo(i,0,9)R(root[i]);
    maketree();
}
inline void update(int v)
{
    f[v]=f[R(A(v))]+f[R(B(v))];
}
void change(int&vx,int&vy,int l,int r,int x,int y)
{
    if(x<=l&&r<=y)
    {
        swap(vx,vy);
        return;
    }
    int mid=(l+r)/2;
    if(x<=mid)change(R(A(vx)),R(A(vy)),l,mid,x,y);
    if(y>mid)change(R(B(vx)),R(B(vy)),mid+1,r,x,y);
    update(vx);update(vy);
}
int find(int v,int l,int r,int x,int y)
{
    if(x<=l&&r<=y)return f[v];
    int mid=(l+r)/2,ans=0;
    if(x<=mid&&A(v))ans=find(A(v),l,mid,x,y);
    if(y>mid&&B(v))ans+=find(B(v),mid+1,r,x,y);
    return ans;
}
int access()
{
    int ans=0;
    while(hu!=hv)
    {
        if(deep[hu]<deep[hv])swap(u,v);
        ans+=find(root[c],1,n,hu,u);
        u=fa[hu];
    }
    if(u>v)swap(u,v);
    return ans+find(root[c],1,n,u,v);
}
void modify()
{
    int i,v;
    fo(i,0,9)
        if(v=find(root[i],1,n,u,u))
        {
            ins(root[i],1,n,u,-v);
            break;
        }
    ins(root[c],1,n,u,val);
}
int main()
{
    scanf("%d",&n);
    fo(i,1,n)scanf("%d",&C[i]);
    fo(i,1,n)scanf("%d",&V[i]);
    fo(i,1,n-1)
    {
        scanf("%d%d",&x,&y);
        x++;y++;
        insert(x,y);
        insert(y,x);
    }

    init();

    scanf("%d",&m);
    fo(i,1,m)
    {
        scanf("%s",&s);
        switch(s[0])
        {
            case 'C':
            {
                scanf("%d%d%d",&u,&x,&y);
                u=dfn[u+1];
                change(root[x],root[y],1,n,u,tail[u]);
                break;
            }
            case 'A':
            {
                scanf("%d%d%d",&u,&v,&c);
                u=dfn[u+1];v=dfn[v+1];
                ans=access();
                printf("%d\n",ans);
                break;
            }
            case 'S':
            {
                scanf("%d%d%d",&u,&c,&val);
                u=dfn[u+1];
                modify();
                break;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值