1. 程式人生 > >【刷題】UOJ #374 【ZJOI2018】歷史

【刷題】UOJ #374 【ZJOI2018】歷史

九條可憐是一個熱愛閱讀的女孩子。

這段時間,她看了一本非常有趣的小說,這本小說的架空世界引起了她的興趣。

這個世界有 \(n\) 個城市,這 \(n\) 個城市被恰好 \(n-1\) 條雙向道路聯通,即任意兩個城市都可以互相到達。同時城市 \(1\) 坐落在世界的中心,佔領了這個城市就稱霸了這個世界。

在最開始,這 \(n\) 個城市都不在任何國家的控制之下,但是隨著社會的發展,一些城市會崛起形成國家並奪取世界的霸權。為了方便,我們標記第 \(i\) 個城市崛起產生的國家為第 \(i\) 個國家。在第 \(i\) 個城市崛起的過程中,第 \(i\) 個國家會取得城市 \(i\) 到城市 \(1\)

路徑上所有城市的控制權。

新的城市的崛起往往意味著戰爭與死亡,若第 \(i\) 個國家在崛起中,需要取得一個原本被國家 \(j\)\(j\ne i\))控制的城市的控制權,那麼國家 \(i\) 就必須向國家 \(j\) 宣戰並進行戰爭。

現在,可憐知道了,在歷史上,第 \(i\) 個城市一共崛起了 \(a_i\) 次。但是這些事件發生的相對順序已經無從考究了,唯一的資訊是,在一個城市崛起稱霸世界之前,新的城市是不會崛起的。

戰爭對人民來說是災難性的。可憐定義一次崛起的災難度為崛起的過程中會和多少不同的國家進行戰爭(和同一個國家進行多次戰爭只會被計入一次)。可憐想要知道,在所有可能的崛起順序中,災難度之和最大是多少。

同時,在考古學家的努力下,越來越多的歷史資料被髮掘了出來,根據這些新的資料,可憐會對 \(a_i\) 進行一些修正。具體來說,可憐會對 \(a_i\) 進行一些操作,每次會將 \(a_x\) 加上 \(w\)。她希望在每次修改之後,都能計算得到最大的災難度。

然而可憐對複雜的計算並不感興趣,因此她想讓你來幫她計算一下這些數值。

對題面的一些補充:

  • 同一個城市多次崛起形成的國家是同一個國家,這意味著同一個城市連續崛起兩次是不會和任何國家開戰的:因為這些城市原來就在它的控制之下。
  • 在歷史的演變過程中,第 $i$ 個國家可能會有一段時間沒有任何城市的控制權。但是這並不意味著第 $i$ 個國家滅亡了,在城市 $i$ 崛起的時候,第 $i$ 個國家仍然會取得 $1$ 到 $i$ 路徑上的城市的控制權。
  • 輸入格式

    第一行輸入兩個整數 \(n,m\) 表示城市個數和操作個數。

    第二行輸入 \(n\) 個整數表示 \(a_i\) 的初始值。

    接下來 \(n-1\) 行,每行輸入兩個整數 \(u_i,v_i\)\(1\le u_i,v_i\le n\))描述了一條道路。

    接下來 \(m\) 行每行輸入兩個整數 \(x_i,w_i\) 表示將 \(a_{x_i}\) 加上 \(w_i\)

    輸出格式

    輸出共 \(m+1\) 行,第一行表示初始的 \(a_i\) 的答案,接下來 \(m\) 行每行表示這次修正後的答案。

    樣例一

    input

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

    output

    6
    7
    9
    10

    explanation

    在修正開始之前,如果按照所在城市 \(4,1,5,3,2\) 的順序崛起,那麼依次會和 \(0,1,2,1,2\) 個國家進行戰爭。這時一共會產生 \(6\) 對敵對關係。可以證明這是所有崛起順序中的最大值。

    樣例二

    見樣例資料下載。

    限制與約定

    測試點 $n$ $m$ 其他約定
    1 $\le 10$ $=0$ $a_i=1$
    2 $\le 150000$ $\le 150000$ 第 $i$ 條道路連線 $i$ 和 $i + 1$
    3
    4 $=0$
    5
    6 $\le 150000$
    7
    8
    9 $\le 4\times 10^5$ $\le 4\times 10^5$
    10

    對於 100% 的資料,\(1\le a_i,w_i\le 10^7\)\(1\le x_i\le n\)

    時間限制:\(2\texttt{s}\)

    空間限制:\(512\texttt{MB}\)

    題解

    首先考慮靜態詢問答案,我們令 \(sum_i\) 表示 \(i\) 的子樹的 \(a_v\) 之和,然後想要求這個點的兒子的子樹中對當前點能產生的最大貢獻

    正常情況下是:\(sum_i-1\),就是每次從不同兒子的子樹中交錯上來

    但是如果存在一個兒子 \(v\)\(sum_v\) 大於 \(sum_i\) 的一半,那麼我們就做不到每次交錯地選,最後一定會有剩下的從同一子樹上來,這樣是無法產生貢獻的(與同一個國家的戰爭只算一次貢獻,這樣的貢獻在兒子處已經計算完了)。這樣的情況下,答案是 \(2(sum_i-\max\{\max\{sum_v\},a_i\})\)

    這樣我們就得到了一個 \(O(n)\) 的靜態演算法

    考慮動態的時候,我們發現修改一個點的 \(a_i\) 改變的就是這個點到根路徑上的 \(sum_f\) 值,我們要對這些點進行修改。這不就想LCT的access嗎,我們只要把重兒子的定義修改為存在上述第二種情況,然後每次修改的時候就可以重新計算貢獻了

    #include<bits/stdc++.h>
    #define ui unsigned int
    #define ll long long
    #define db double
    #define ld long double
    #define ull unsigned long long
    #define ft first
    #define sd second
    #define pb(a) push_back(a)
    #define PII std::pair<int,int>
    #define PLL std::pair<ll,ll>
    #define mp(a,b) std::make_pair(a,b)
    #define ITR(a,b) for(auto a:b)
    #define REP(a,b,c) for(register int a=(b),a##end=(c);a<=a##end;++a)
    #define DEP(a,b,c) for(register int a=(b),a##end=(c);a>=a##end;--a)
    const int MAXN=400000+10;
    int n,m,hson[MAXN],e,beg[MAXN],nex[MAXN<<1],to[MAXN<<1];
    ll size[MAXN],a[MAXN],ans,val[MAXN];
    template<typename T> inline void read(T &x)
    {
        T data=0,w=1;
        char ch=0;
        while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
        if(ch=='-')w=-1,ch=getchar();
        while(ch>='0'&&ch<='9')data=((T)data<<3)+((T)data<<1)+(ch^'0'),ch=getchar();
        x=data*w;
    }
    template<typename T> inline void write(T x,char ch='\0')
    {
        if(x<0)putchar('-'),x=-x;
        if(x>9)write(x/10);
        putchar(x%10+'0');
        if(ch!='\0')putchar(ch);
    }
    template<typename T> inline bool chkmin(T &x,T y){return y<x?(x=y,true):false;}
    template<typename T> inline bool chkmax(T &x,T y){return y>x?(x=y,true):false;}
    template<typename T> inline T min(T x,T y){return x<y?x:y;}
    template<typename T> inline T max(T x,T y){return x>y?x:y;}
    inline void calc(int x)
    {
        ans-=val[x];
        val[x]=hson[x]?2*(size[x]-size[hson[x]]):size[x]-1;
        if(a[x]*2>size[x]+1)val[x]=2*(size[x]-a[x]);
        ans+=val[x];
    }
    #define lc(x) ch[(x)][0]
    #define rc(x) ch[(x)][1]
    struct LinkCut_Tree{
        int fa[MAXN],ch[MAXN][2],stk[MAXN],cnt;
        ll add[MAXN];
        inline bool nroot(int x)
        {
            return lc(fa[x])==x||rc(fa[x])==x;
        }
        inline void plus(int x,ll v)
        {
            size[x]+=v;add[x]+=v;
        }
        inline void pushdown(int x)
        {
            if(add[x])
            {
                if(lc(x))plus(lc(x),add[x]);
                if(rc(x))plus(rc(x),add[x]);
                add[x]=0;
            }
        }
        inline void rotate(int x)
        {
            int f=fa[x],p=fa[f],c=(rc(f)==x);
            if(nroot(f))ch[p][rc(p)==f]=x;
            fa[ch[f][c]=ch[x][c^1]]=f;
            fa[ch[x][c^1]=f]=x;
            fa[x]=p;
        }
        inline void splay(int x)
        {
            cnt=0;
            stk[++cnt]=x;
            for(register int i=x;nroot(i);i=fa[i])stk[++cnt]=fa[i];
            while(cnt)pushdown(stk[cnt--]);
            for(register int y=fa[x];nroot(x);rotate(x),y=fa[x])
                if(nroot(y))rotate((lc(y)==x)==(lc(fa[y])==y)?y:x);
        }
        inline int findroot(int x)
        {
            while(lc(x))pushdown(x),x=lc(x);
            return x;
        }
        inline void access(int x,int v)
        {
            for(register int y=0;x;x=fa[y=x])
            {
                splay(x);size[x]+=v;
                if(lc(x))plus(lc(x),v);
                if(hson[x])
                {
                    cnt=0;
                    stk[++cnt]=hson[x];
                    for(register int i=hson[x];nroot(i);i=fa[i])stk[++cnt]=fa[i];
                    while(cnt)pushdown(stk[cnt--]);
                    if(size[hson[x]]*2<=size[x]+1)hson[x]=rc(x)=0;
                }
                int nrt=findroot(y);
                if(size[nrt]*2>size[x]+1)hson[x]=nrt,rc(x)=y;
                calc(x);
            }
        }
    };
    LinkCut_Tree T;
    #undef lc
    #undef rc
    inline void insert(int x,int y)
    {
        to[++e]=y;
        nex[e]=beg[x];
        beg[x]=e;
    }
    inline void dfs(int x,int f)
    {
        ll res=0;
        size[x]=a[x];T.fa[x]=f;
        for(register int i=beg[x];i;i=nex[i])
            if(to[i]==f)continue;
            else
            {
                dfs(to[i],x);
                size[x]+=size[to[i]];
                if(chkmax(res,size[to[i]]))hson[x]=to[i];
            }
        if(size[hson[x]]*2<=size[x])hson[x]=0;
        T.ch[x][1]=hson[x];
        calc(x);
    }
    int main()
    {
        read(n);read(m);
        REP(i,1,n)read(a[i]);
        REP(i,1,n-1)
        {
            int u,v;read(u);read(v);
            insert(u,v);insert(v,u);
        }
        dfs(1,0);
        printf("%lld\n",ans);
        while(m--)
        {
            int x,w;read(x);read(w);
            a[x]+=w;T.access(x,w);
            printf("%lld\n",ans);
        }
        return 0;
    }