[BZOJ5212][ZJOI2018]历史-Link-Cut-Tree

历史

Description

九条可怜是一个热爱阅读的女孩子。

这段时间,她看了一本非常有趣的小说,这本小说的架空世界引起了她的兴趣。

这个世界有n个城市,这n个城市被恰好n?1条双向道路联通,即任意两个城市都可以互相到达。同时城市1坐落在世界的中心,占领了这个城市就称霸了这个世界。

在最开始,这n个城市都不在任何国家的控制之下,但是随着社会的发展,一些城市会崛起形成国家并夺取世界的霸权。为了方便,我们标记第i个城市崛起产生的国家为第i个国家。在第i个城市崛起的过程中,第i个国家会取得城市i到城市1路径上所有城市的控制权。

新的城市的崛起往往意味着战争与死亡,若第i个国家在崛起中,需要取得一个原本被国家j(j!=i)控制的城市的控制权,那么国家i就必须向国家j宣战并进行战争。

现在,可怜知道了,在历史上,第i个城市一共崛起了ai次。但是这些事件发生的相对顺序已经无从考究了,唯一的信息是,在一个城市崛起称霸世界之前,新的城市是不会崛起的。

战争对人民来说是灾难性的。可怜定义一次崛起的灾难度为崛起的过程中会和多少不同的国家进行战争(和同一个国家进行多次战争只会被计入一次)。可怜想要知道,在所有可能的崛起顺序中,灾难度之和最大是多少。

同时,在考古学家的努力下,越来越多的历史资料被发掘了出来,根据这些新的资料,可怜会对ai进行一些修正。具体来说,可怜会对ai进行一些操作,每次会将ax加上w。她希望在每次修改之后,都能计算得到最大的灾难度。

然而可怜对复杂的计算并不感兴趣,因此她想让你来帮她计算一下这些数值。

对题面的一些补充:

1:同一个城市多次崛起形成的国家是同一个国家,这意味着同一个城市连续崛起两次是不会和任何国家开战的:因为这些城市原来就在它的控制之下。

2:在历史的演变过程中,第i个国家可能会有一段时间没有任何城市的控制权。但是这并不意味着第i个国家灭亡了,在城市i崛起的时候,第i个国家仍然会取得1到i路径上的城市的控制权

Input

第一行输入两个整数n,m表示城市个数和操作个数。
第二行输入n个整数表示ai的初始值。
接下来n-1行,每行输入两个整数ui,vi(1<=ui,vi<=n)描述了一条道路。
接下来m行每行输入两个整数xi,wi表示将axi加上wi
N<=4*10^5
1 <= ai, wi <= 10^7, 1 <= xi <= n

Output

输出共m+1行,第一行表示初始的ai的答案,接下来m行每行表示这次修正后的答案

Sample Input

5 3
1 1 1 1 1
1 2
1 3
2 4
2 5
2 1
3 1
4 1

Sample Output

6
7
9
10

在修正开始之前,如果按照所在城市4,1,5,3,2的顺序崛起,那么依次会和0,1,2,1,2个国家进行战争。这时一共会产生6对敌对关系。可以证明这是所有崛起顺序中的最大值。


《论考场debuff的危害性》
考场上咱们机房几乎无人写出10分以上的分数……
(某XZK dalao除外(见友链))


思路:

可以发现每个城市的贡献是独立的,那么分开考虑。

观察得出在一个城市 u 发生一次新的宣战事件的条件:
首先,显然只有 u 的子树中的节点的崛起可能引起 u 处产生宣战,因此只考虑这个子树中的节点。
此时,若相邻的两次崛起事件发生了宣战,那么两次事件的节点需要满足,两个节点不能来自 u 的同一个儿子的子树,并且不能同时为 u 本身。

考虑最大化单个节点的宣战次数。
可以发现,最优方案一定满足每对相邻的崛起事件尽可能不来自同一个儿子的子树。
此时,可以发现唯一会对答案产生影响的只有同一儿子的子树中崛起事件之和的最大值的大小。

于是,对于所有 vch[u] ,令 Av v 子树中所有节点崛起次数之和,同时令 Au 表示 u 节点崛起的次数。
t=Au+vch[u]Avh=max{Au,maxvch[u]{Av}}
可以得出,最优答案为 min{t1,2(th)} 。也就是说,当 2ht+1 ,答案为 2(th) ,否则为 t1

于是对整棵树跑一边树形dp,就可以就可以得到 30pts 了!

然而这不兹瓷修改。
于是咱们需要动态维护这个dp值。

fi 为节点 i 子树中的 a 值之和。
定义实边为,满足 2fiffa[i]+1 的,连接 (i,fa[i]) 的边。
可以发现,每个节点最只会有不多于一条的实边,且每条实边中深度较浅的点的贡献将形如 2(th)

考虑一次修改。
提取这次修改到根节点的路径,可以发现实边依旧是实边,而且实边对答案的贡献,即 2(th) 不变。
因此,只有虚边可能使贡献产生变化,并且虚边有可能在途中变成实边。

可以发现寻找虚边的过程很像 LCT access ,只是 access 沿路经过的所有边一定会变成实边,而这里不会。
于是考虑使用 LCT 维护若干实边组成的路径,每次修改时暴力找到所有实边并重新计算对应贡献即可~

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=4e5+9;

int n,m;
ll ans,a[N];
int to[N<<1],nxt[N<<1],beg[N],tot;

inline ll read()
{
    ll x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

inline ll minn(ll a,ll b){return a<b?a:b;}
inline ll maxx(ll a,ll b){return a>b?a:b;}
inline void chkmax(ll &a,ll b){if(a<b)a=b;}

inline ll calc(ll t,ll h)
{
    return t?minn(2*(t-h),t-1):t;
}

namespace lct
{
    int ch[N][2],fa[N],tot;
    ll val[N],sum[N],vsum[N];

    inline bool isroot(int x)
    {
        return ch[fa[x]][0]!=x && ch[fa[x]][1]!=x;
    }

    inline void update(int x)
    {
        sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+val[x]+vsum[x];
    }

    inline void rotate(int x)
    {
        int y=fa[x],z=fa[y];
        int l=(ch[y][1]==x);
        if(!isroot(y))ch[z][ch[z][1]==y]=x;
        fa[x]=z;fa[y]=x;fa[ch[x][l^1]]=y;
        ch[y][l]=ch[x][l^1];ch[x][l^1]=y;
        update(y);update(x);
    }

    inline void splay(int x)
    {
        for(int y=fa[x],z=fa[y];!isroot(x);rotate(x),y=fa[x],z=fa[y])
            if(!isroot(y))
            {
                if(ch[z][1]==y^ch[y][1]==x)rotate(x);
                else rotate(y);
            }
        update(x);
    }

    inline void modify(int u,ll v)
    {
        splay(u);val[u]+=v;
        update(u);

        ll tsum=sum[u]-sum[ch[u][0]],t;
        if(ch[u][1])
        {
            ll hson=sum[ch[u][1]];
            ans+=calc(tsum,maxx(hson,val[u]))-calc(tsum-v,hson);
            if(hson*2<tsum+1)
            {
                vsum[u]+=hson;
                ch[u][1]=0;
            }
        }
        else
            ans+=calc(tsum,val[u])-calc(tsum-v,val[u]-v);

        update(u);
        for(t=u,u=fa[u];u;t=u,u=fa[u])
        {
            splay(u);vsum[u]+=v;
            update(u);

            tsum=sum[u]-sum[ch[u][0]];
            if(ch[u][1])
            {
                ll hson=sum[ch[u][1]];
                ans+=calc(tsum,maxx(hson,sum[t]))-calc(tsum-v,hson);

                if(hson*2<tsum+1)
                {
                    vsum[u]+=hson;
                    ch[u][1]=0;
                }
            }
            else
                ans+=calc(tsum,maxx(val[u],sum[t]))-calc(tsum-v,val[u]);

            if(sum[t]*2>=tsum+1)
            {
                vsum[u]-=sum[t];
                ch[u][1]=t;
            }
            update(u);
        }
    }
}

inline void add(int u,int v)
{
    to[++tot]=v;
    nxt[tot]=beg[u];
    beg[u]=tot;
}

inline void dfs(int u)
{
    for(int i=beg[u];i;i=nxt[i])
        if(to[i]!=lct::fa[u])
        {
            lct::fa[to[i]]=u;
            dfs(to[i]);
        }
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    for(int i=1,u,v;i<n;i++)
    {
        u=read();v=read();
        add(u,v);add(v,u);
    }

    dfs(1);
    for(int i=1;i<=n;i++)
        lct::modify(i,a[i]);
    printf("%lld\n",ans);

    while(m--)
    {
        int x=read();ll w=read();
        lct::modify(x,w);
        printf("%lld\n",ans);
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/zlttttt/article/details/79683188