[ZJOI2018]历史

Posted creed-qwq

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[ZJOI2018]历史相关的知识,希望对你有一定的参考价值。

吉老师天下第一!

感觉这个题大概能算我见过的最神仙的数据结构题?

首先考虑把答案拆到每一个点上,即去计算每一个点会被贡献多少次。

显然,对于一个点来说,只有它子树内的崛起可能会在它这里产生贡献。

具体一点,如果它子树内部连续崛起的两个点属于两个不同的儿子,那么贡献+1。

那么就转化为这样找一个问题。

有n个物品,按照颜色划分为k种,每种有a1,a2,a3..ak个。

把这些物品摆成一排,两个相邻的物品如果颜色不同,那么贡献+1,求最大贡献。

结论:

设p为a数组最大值,则ans=min(n-1,2*(n-p))

证明:

首先上界显然是n-1。

如果最大值超过一半的话,那么最大值一定会发生“同色相邻”的情况,只能产生n-p对贡献。

如果最大值不超过一半的话,那么就先把最大值排成一排,然后再找出次大值,依次插入每一个间隔中,递归着做下去,就可以达到上界n-1。

有了这个结论,就可以通过简单树形dp解决没有修改的情况。

如果带修改怎么做?

分析一下那个式子,考虑答案取到n-1的条件----------->2k<n+1,即2k<=n。

我们考虑进行一波轻重链剖分,

对于2k<=n的边连轻边,对于2k>n的边连重边。

这样可以保证任何一个点到根的轻边数量不会超过logn。

然后用LCT来大力维护这个东西。

用一个数组,存一下当前每个点在min(n-1,2(n-k))具体是怎么被贡献的。

修改的时候,对于重链,由于取到的答案是2(n-k),同时变大相同的量,无需修改。

对于轻边,判断一下修改后是否大于一半,如果大于一半就把轻边改为重边。

同理,如果父亲加上一个权值后是的原本的重儿子变为轻儿子,也要修改一下。

#include<bits/stdc++.h>
#define N 440000
#define eps 1e-7
#define inf 1e9+7
#define db double
#define ll long long
#define ldb long double
using namespace std;
inline ll read()
{
    char ch=0;
    ll x=0,flag=1;
    while(!isdigit(ch)){ch=getchar();if(ch=='-')flag=-1;}
    while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*flag;
}
#define lson son[x][0]
#define rson son[x][1]
ll f[N],v[N],sw[N],sv[N],flag[N],son[N][2];
bool get(ll x){return son[f[x]][1]==x;}
bool isrt(ll x){return x!=son[f[x]][0]&&x!=son[f[x]][1];}
void pushup(ll x){sw[x]=sw[lson]+sw[rson]+sv[x]+v[x];}
void rotate(ll x)
{
    ll y=f[x],z=f[y],tx=get(x),ty=get(y),p=son[x][!tx];
    if(!isrt(y))son[z][ty]=x;son[x][!tx]=y;son[y][tx]=p;
    if(p)f[p]=y;f[x]=z;f[y]=x;pushup(y);pushup(x);
}
void splay(ll x)
{
    while(!isrt(x))
    {
        ll y=f[x];
        if(!isrt(y))rotate(get(x)==get(y)?x:y);
        rotate(x);
    }
}
struct edge{ll to,nxt;}e[N*2];
ll num,head[N];
inline void add(ll x,ll y){e[++num]={y,head[x]};head[x]=num;}
ll n,m,ans=0;
void dfs(ll x,ll fa)
{
    ll k=x,mx=v[x],tot=v[x];
    for(ll i=head[x];i!=-1;i=e[i].nxt)
    {
        ll to=e[i].to;
        if(to==fa)continue;
        dfs(to,x);
        f[to]=x;tot+=sw[to];
        if(mx<sw[to])k=to,mx=sw[to];
    }
    sv[x]=tot-v[x];sw[x]=tot;
    if(k!=x&&2*mx>tot){flag[x]=0;rson=k;sv[x]-=mx;ans+=2*(tot-mx);return;}
    if(k==x&&2*mx>tot){flag[x]=1;ans+=2*(tot-v[x]);return;}
    if(2*mx<=tot){flag[x]=2;ans+=tot-1;return;}
}
int main()
{
    n=read();m=read();
    for(ll i=1;i<=n;i++)v[i]=read();
    num=-1;memset(head,-1,sizeof(head));
    for(ll i=2;i<=n;i++){ll x=read(),y=read();add(x,y);add(y,x);}
    dfs(1,1);printf("%lld\n",ans);
    for(ll i=1;i<=m;i++)
    {
        ll x=read(),k=read();
        for(ll y=0;x;y=x,x=f[x])
        {
            splay(x);
            ll s=sw[x]-sw[lson];
            if(flag[x]==0)ans-=2*(s-sw[rson]);//某个子树大于等于n/2 
            if(flag[x]==1)ans-=2*(s-v[x]);//自己大于等于n/2 
            if(flag[x]==2)ans-=s-1;//都小于n/2
            s+=k;sw[x]+=k;if(!y)v[x]+=k;else sv[x]+=k;
            if(2*sw[y]>s)sv[x]+=sw[rson],rson=y,sv[x]-=sw[rson];
            if(2*sw[rson]>s)flag[x]=0,ans+=2*(s-sw[rson]);//某个子树大于等于n/2
            else
            {
                sv[x]+=sw[rson];rson=0;
                if(2*v[x]>s)flag[x]=1,ans+=2*(s-v[x]);//自己大于等于n/2 
                else flag[x]=2,ans+=s-1;//都小于n/2
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

以上是关于[ZJOI2018]历史的主要内容,如果未能解决你的问题,请参考以下文章

ZJOI 2018 历史

[ZJOI2018]历史

[ZJOI2018]历史

刷题UOJ #374 ZJOI2018历史

zjoi[ZJOI2018]胖

ZJOI 2018 Day 2 胖