[XJOI]黑白树

题目大意

有一棵以 1 为根的n个点的树,每个点有黑白两种颜色以及一个权值(一开始都是零)。
q 个操作,有以下两种:
 选定一个点 x ,将所有黑色节点y的权值加上 lca(x,y)
  x 号点的颜色反色
在所有操作执行完后,请输出所有点的权值。

1n,q5×104


题目分析

将每个点的编号都和父亲编号做差,这样一个点对权值的贡献就可以看成起到根路径的和再乘上这个点作为 1 操作的点的祖先的次数。
考虑使用数据结构维护路径上的一个计数器,计算一条路径的编号差与次数乘积之和。因为每次只是修改黑色节点,我们使用差分的思想,当一个点变成黑色点的时候我们把它的答案减去其到根路径的权值和,变成白色时加上这个权值和。最后再处理没有变回白色的点。
数据结构使用LCT即可做到 O(nlogn)


代码实现

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>
#include <stack>

using namespace std;

typedef long long LL;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

int buf[30];

void write(LL x)
{
    if (x<0) putchar('-'),x=-x;
    for (;x;x/=10) buf[++buf[0]]=x%10;
    if (!buf[0]) buf[++buf[0]]=0;
    for (;buf[0];putchar('0'+buf[buf[0]--]));
}

const int N=50050;
const int E=N<<1;

int fa[N],last[N];
int tov[E],nxt[E];
bool col[N];
LL ans[N];
int n,q,tot;

namespace link_cut_tree
{
    int fa[N],size[N],par[N],tag[N],val[N];
    LL sum[N],f[N],valsum[N];
    stack<int> st;
    int son[N][2];
    bool mark[N];

    bool side(int x){return son[fa[x]][1]==x;}

    void update(int x){size[x]=size[son[x][0]]+size[son[x][1]]+1,valsum[x]=valsum[son[x][0]]+valsum[son[x][1]]+val[x],sum[x]=sum[son[x][0]]+sum[son[x][1]]+f[x];}

    void ADD(int x,int delta){tag[x]+=delta,f[x]+=1ll*delta*val[x],sum[x]+=1ll*delta*valsum[x];}

    void R(int x){swap(son[x][0],son[x][1]),mark[x]^=1;}

    void clear(int x)
    {
        if (mark[x])
        {
            if (son[x][0]) R(son[x][0]);
            if (son[x][1]) R(son[x][1]);
            mark[x]=0;
        }
        if (tag[x])
        {
            if (son[x][0]) ADD(son[x][0],tag[x]);
            if (son[x][1]) ADD(son[x][1],tag[x]);
            tag[x]=0;
        }
    }

    void rotate(int x)
    {
        int y=fa[x];bool s=side(x);
        if (fa[y]) son[fa[y]][side(y)]=x;
        if (son[x][s^1]) fa[son[x][s^1]]=y;
        son[y][s]=son[x][s^1],son[x][s^1]=y;
        fa[x]=fa[y],fa[y]=x;
        if (par[y]) par[x]=par[y],par[y]=0;
        update(y),update(x);
    }

    void pushdown(int x,int y)
    {
        for (;x!=y;st.push(x),x=fa[x]);
        for (;!st.empty();clear(st.top()),st.pop());
    }

    void splay(int x,int y)
    {
        for (pushdown(x,y);fa[x]!=y;rotate(x))
            if (fa[fa[x]]!=y)
                if (side(x)==side(fa[x])) rotate(fa[x]);
                else rotate(x);
    }

    int access(int x)
    {
        int nxt=0;
        for (;x;update(nxt=x),x=par[x])
        {
            splay(x,0);
            if (son[x][1]) par[son[x][1]]=x,fa[son[x][1]]=0;
            if (nxt) par[nxt]=0,fa[nxt]=x;
            son[x][1]=nxt;
        }
        return nxt;
    }

    void makeroot(int x){R(access(x));}

    void modify(int x){makeroot(1),access(x),splay(x,0),ADD(x,1);}

    LL query(int x){return makeroot(1),access(x),splay(x,0),sum[x];}
};

void insert(int x,int y){tov[++tot]=y,nxt[tot]=last[x],last[x]=tot;}

void dfs(int x)
{
    for (int i=last[x],y;i;i=nxt[i])
        if ((y=tov[i])!=fa[x]) fa[y]=x,dfs(y);
    link_cut_tree::val[x]=x-fa[x],link_cut_tree::update(x),link_cut_tree::par[x]=fa[x];
}

int main()
{
    freopen("bwt.in","r",stdin),freopen("bwt.out","w",stdout);
    n=read(),q=read();
    for (int i=1;i<=n;++i) col[i]=read();
    for (int i=1,x,y;i<n;++i) x=read(),y=read(),insert(x,y),insert(y,x);
    fa[1]=0,dfs(1);
    for (int tp,x;q--;)
    {
        tp=read()-1,x=read();
        if (tp) ans[x]+=(col[x]?1:-1)*link_cut_tree::query(x),col[x]^=1;
        else link_cut_tree::modify(x);
    }
    for (int x=1;x<=n;++x) if (col[x]) ans[x]+=link_cut_tree::query(x);
    for (int x=1;x<=n;++x) write(ans[x]),putchar('\n');
    fclose(stdin),fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值