[ZJOI 2018] 历史

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/DT_Kang/article/details/81138582

题目大意:给定一棵树,以及每个点 access 的次数,求轻重链切换次数最大值。带修改,每次给每个点加 w 次。

考虑不带修改怎么做。可以发现,如果给 u 安排一个最优序列,那么给它的祖先安排顺序的时候是不会和它冲突的(因为考虑 u 的祖先的时候 u 中的每个点就是等价的了)。因此我们可以每个点单独计算。那么对于一个点 u,问题可以转化成有一些颜色的球,每种颜色有 a i 个,求一种排列方式使得相邻且颜色不同的颜色最多。容易发现,如果每种球数量比较平均,答案就是球的数量减一。如果有一种球大于总数的一半,那么答案是 ( s u m m a x ) 2

考虑带修改。发现我们不需要关注那些已经超过子树一半的点。如果我们把每个点和它的“重儿子”连实边,和它其他的儿子连虚边,那么我们修改影响的一定只有轻边。而且证明,一个点到根的路径上经过不超过 log n 条轻边。用 LCT 维护这个过程:找到一条虚边,更改信息,检查是否需要更改连边。

这里我们需要用 LCT 维护子树信息。做法是这样的:额外记录一下一个点所有“虚儿子”(fa 数组指向他的儿子)的信息。update 的时候用 splay 里的左右儿子的总信息和自己的虚儿子的信息和自己的信息合并。事实上,这样每个点的总信息表示的其实是他所在的 splay 的子树中的每个点的子树信息。

细节挺多的。

#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;
struct edge{
    int to,next;
}ed[800010];
int fa[400010],opt[400010],head[400010],sz,son[400010][2];
ll sum[400010],sumi[400010],ans,a[400010];
void add_edge(int from,int to)
{
    ed[++sz].to=to;
    ed[sz].next=head[from];
    head[from]=sz;
}
inline int read()
{
    int x=0,flag=1;char c=getchar();
    while(!isdigit(c)) {if(c=='-') flag=-1;c=getchar();}
    while(isdigit(c)) x=x*10+c-'0',c=getchar();
    return x*flag;
}
bool not_root(int x)
{
    if(son[fa[x]][0]==x||son[fa[x]][1]==x) return true;
    return false;
}
void push_up(int x)
{
    sum[x]=sum[son[x][0]]+sum[son[x][1]]+a[x]+sumi[x];
}
void rotate(int x)
{
    int y=fa[x],z=fa[y],kind= x==son[y][0];
    int tmp=not_root(y);
    son[y][!kind]=son[x][kind];
    if(son[x][kind]) fa[son[x][kind]]=y;
    son[x][kind]=y;
    fa[y]=x;
    fa[x]=z;
    if(tmp)
    {
        if(y==son[z][0]) son[z][0]=x;
        else son[z][1]=x;
    }
    push_up(y);
    push_up(x);
}
void splay(int x)
{
    while(not_root(x))
    {
        int y=fa[x],z=fa[y];
        if(not_root(y)) 
        {
            if((x==son[y][0])^(y==son[z][0])) rotate(x);
            else rotate(y);
        }
        rotate(x);
    }
    push_up(x);
}
void access(int x,int w)
{
    for(int y=0;x;x=fa[y=x])
    {
        splay(x);
        ll tmp=sum[x]-sum[son[x][0]];
        if(opt[x]==0) ans-=tmp-1;
        else if(opt[x]==1) ans-=(tmp-sum[son[x][1]])*2;
        else ans-=(tmp-a[x])*2;
        tmp+=w,sum[x]+=w;
        if(y) sumi[x]+=w;
        else a[x]+=w;
        if(sum[y]*2>tmp+1) sumi[x]+=sum[son[x][1]],sumi[x]-=sum[son[x][1]=y];
        if(sum[son[x][1]]*2>tmp+1) opt[x]=1,ans+=2*(tmp-sum[son[x][1]]);
        else 
        {
            if(son[x][1]) sumi[x]+=sum[son[x][1]],son[x][1]=0;
            if(a[x]*2>tmp+1) opt[x]=2,ans+=2*(tmp-a[x]);
            else opt[x]=0,ans+=tmp-1,son[x][1]=0;
        }
    }
}
void dfs(int u,int fff)
{
    sum[u]=a[u];
    ll Max=a[u],p=u;
    for(int i=head[u];i;i=ed[i].next)
    {
        int v=ed[i].to;
        if(v==fff) continue;
        fa[v]=u;
        dfs(v,u);
        sumi[u]+=sum[v];
        if(sum[v]>Max) Max=sum[v],p=v;
    }
    sum[u]+=sumi[u];
    if(Max*2>sum[u]+1)
    {
        ans+=2*(sum[u]-Max);
        if(p!=u) opt[u]=1,sumi[u]-=sum[p],son[u][1]=p;
        else opt[u]=2;
    }
    else ans+=sum[u]-1;
}
int main()
{
    int n=read(),m=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<n;i++) 
    {
        int u=read(),v=read();
        add_edge(u,v);
        add_edge(v,u);
    }
    dfs(1,1);
    cout<<ans<<endl;
    for(int i=1;i<=m;i++)
    {
        int u=read(),w=read();
        access(u,w);
        cout<<ans<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/DT_Kang/article/details/81138582