bzoj 5212: [Zjoi2018]历史 lct

题意

给一棵n个节点的树,每个节点有一个access次数 ax a x 。现在可以以任意顺序来access每个节点,要求轻重链切换次数总和最大。同时还有修改,每次会把a[x]加上一个正整数。
n<=400000

分析

我们考虑一个节点x的轻重链切换次数。
不难发现只有当相邻的两次操作不在同一棵子树中或一次为x另一次不为x时有1的贡献。
如果我们把 ax a x 看做 A0 A 0 ,第i棵子树大小看做 Ai A i ,那么可以看成现在有 t=ki=0Ai t = ∑ i = 0 k A i 个小球,每个小球有一种颜色。现在要求以某种顺序摆放小球,使得相邻且不同颜色的小球对数尽量大。
h=maxki=0Ai h = max i = 0 k A i ,不难发现答案就等于 min(t1,2(th)) m i n ( t − 1 , 2 ( t − h ) ) ,也就是当 2ht+1 2 h ≥ t + 1 时会取到后者。
现在我们已经可以轻易求出m=0时的答案了,考虑如何支持修改操作。
fi f i 表示i子树中的access次数和。
2fiffa+1 2 f i ≥ f f a + 1 时,我们把 (fa,i) ( f a , i ) 这条边设为重边,其余设为轻边。
不难发现每个点往下最多只有一条重边,且每个点到根路径上的轻边数不超过 O(logai) O ( l o g ∑ a i )
当现在要把 a[x] a [ x ] 加上 w w 时,显然重边仍然是重边,因为th没有改变而 h h 增大了;而轻边则有可能变成重边。
那么我们就像lct的access操作那样去维护每条轻边即可。
时间复杂度O(nlogai)

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;

const int N=400004;

int n,m,cnt,last[N];
struct edge{int to,next;}e[N*2];
LL ans,f[N],a[N],b[N];
struct tree{int l,r,fa,s;LL f,tag;}t[N];
bool leaf[N];

int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

void addedge(int u,int v)
{
    e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;
    e[++cnt].to=u;e[cnt].next=last[v];last[v]=cnt;
}

bool is_root(int x)
{
    return x!=t[t[x].fa].l&&x!=t[t[x].fa].r;
}

void updata(int x)
{
    t[x].s=t[t[x].l].s+t[t[x].r].s+1;
}

void mark(int x,LL y)
{
    t[x].tag+=y;t[x].f+=y;
}

void pushdown(int x)
{
    LL w=t[x].tag;t[x].tag=0;
    if (t[x].l) mark(t[x].l,w);
    if (t[x].r) mark(t[x].r,w);
}

void remove(int x)
{
    if (!is_root(x)) remove(t[x].fa);
    pushdown(x);
}

void rttl(int x)
{
    int y=t[x].r;
    t[x].r=t[y].l;t[t[y].l].fa=t[y].l?x:0;
    if (x==t[t[x].fa].l) t[t[x].fa].l=y;
    else if (x==t[t[x].fa].r) t[t[x].fa].r=y;
    t[y].fa=t[x].fa;t[y].l=x;t[x].fa=y;
    updata(x);updata(y);
}

void rttr(int x)
{
    int y=t[x].l;
    t[x].l=t[y].r;t[t[y].r].fa=t[y].r?x:0;
    if (x==t[t[x].fa].l) t[t[x].fa].l=y;
    else if (x==t[t[x].fa].r) t[t[x].fa].r=y;
    t[y].fa=t[x].fa;t[y].r=x;t[x].fa=y;
    updata(x);updata(y);
}

void splay(int x)
{
    if (!x) return;
    remove(x);
    while (!is_root(x))
    {
        int p=t[x].fa,g=t[p].fa;
        if (is_root(p))
        {
            if (x==t[p].l) rttr(p);
            else rttl(p);
            break;
        }
        if (x==t[p].l)
            if (p==t[g].l) rttr(g),rttr(p);
            else rttr(p),rttl(g);
        else
            if (p==t[g].r) rttl(g),rttl(p);
            else rttl(p),rttr(g);
    }
}

int get_nx(int x)
{
    x=t[x].r;
    while (t[x].l) x=t[x].l;
    splay(x);
    return x;
}

int get_fir(int x)
{
    while (t[x].l) x=t[x].l;
    splay(x);
    return x;
}

void access(int x,int w)
{
    int y=0;
    while (x)
    {
        splay(x);int nx=get_nx(x);splay(x);
        if (!leaf[x]) ans-=!nx?min(t[x].f-1,(t[x].f-b[x])*2):(t[x].f-t[nx].f)*2;
        t[x].f+=w;
        if (t[nx].f*2<t[x].f+1) t[x].r=0,updata(x);
        if (t[y].f*2>=t[x].f+1) t[x].r=y,t[y].fa=x,updata(x);
        if (!leaf[x])
            if (y&&t[x].r==y) ans+=(t[x].f-t[y].f)*2;
            else if (t[x].r) ans+=(t[x].f-t[nx].f)*2;
            else ans+=min(t[x].f-1,(t[x].f-a[x])*2);
        if (t[x].l) mark(t[x].l,w);
        x=get_fir(x);splay(x);y=x;x=t[x].fa;
    }
}

void dfs(int x,int fa)
{
    f[x]=a[x];LL mx=a[x];leaf[x]=1;
    for (int i=last[x];i;i=e[i].next)
    {
        if (e[i].to==fa) continue;
        t[e[i].to].fa=x;
        dfs(e[i].to,x);
        f[x]+=f[e[i].to];
        leaf[x]=0;
        mx=max(mx,f[e[i].to]);
    }
    t[x].f=f[x];
    ans+=min((f[x]-mx)*2,f[x]-1);
    for (int i=last[x];i;i=e[i].next)
        if (e[i].to!=fa&&f[e[i].to]*2>=f[x]+1) t[x].r=e[i].to,updata(x);
}

int main()
{
    n=read();m=read();
    for (int i=1;i<=n;i++) a[i]=b[i]=read();
    for (int i=1;i<n;i++)
    {
        int x=read(),y=read();
        addedge(x,y);
    }
    for (int i=1;i<=n;i++) t[i].s=1;
    dfs(1,0);
    printf("%lld\n",ans);
    while (m--)
    {
        int x=read(),w=read();
        a[x]+=w;access(x,w);b[x]+=w;
        printf("%lld\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值