1. 程式人生 > 其它 >題解 「ZJOI2018」歷史

題解 「ZJOI2018」歷史

題目傳送門

Description

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

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

這個世界有 \(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\) 其他約定
1 \(\le 10\) \(=0\) \(a_i=1\)
2-3 \(\le 150000\) \(\le 150000\) \(i\) 條道路連線 \(i\)\(i + 1\)
4-5 \(\le 150000\) \(=0\) -
6-8 \(\le 150000\) \(\le 150000\) -
9-10 \(\le 4 \times 10^5\) \(\le 4\times 10^5\) -

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

Solution

真的很難啊。/kk

首先考慮沒有修改的時候我們怎麼做。我們可以考慮計算一個點會產生的貢獻,可以想到的是,城市 \(i\) 對城市 \(j\) 產生的貢獻僅在 \(\text{lca}(i,j)\) 處產生,所以我們列舉當前節點為 lca,那麼每個子樹就相當於一種顏色,子樹內 access 操作總數就是該顏色個數,最多產生的貢獻就是確定一種顏色序列相鄰兩個不同色個數,即:

\[\min\{t-1,2(t-h)\} \]

其中 \(t\) 是顏色總數,\(h\) 是顏色個數最多的個數。其意義是,若出現次數最多的顏色個數超過一半,就把其他顏色往該顏色交錯放,否則就可以達到上屆 \(t-1\)

可以想到的是,我的 lca 不會影響子樹放的方法,所以每個點都可以達到最優情況。

考慮增加了修改操作,我們可以使用 lct 進行維護。我們可以設 \(f_i\) 表示以 \(i\) 為根的子樹內 access 操作總數,那麼我們存在邊 \((u,fa_u)\) 當且僅當 \(2f_u\ge f_{fa_u}+1\) 。可以想到這個時候 \(fa_u\) 的貢獻一定是 \(2(f_{fa_u}-f_u)\)

那麼考慮增值操作 \((u,w)\),可以發現只會影響 \(1\to u\) 上的點,而且鏈上實邊仍會是實邊,因為 \(2f_u\ge f_{fa_u}+1\rightarrow 2(f_u+w)\ge f_{fa_u}+w+1\) 。而這也滿足 lct 所需性質,所以我們可以做到複雜度 \(\Theta(n\log n)\)

Code

#include <bits/stdc++.h>

using namespace std;



#define Int register int

#define int long long

#define MAXN 400005



template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}

template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}

template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}

template <typename T> inline void chkmax (T &a,T b){a = max (a,b);}

template <typename T> inline void chkmin (T &a,T b){a = min (a,b);}



int n,m;

vector <int> g[MAXN];



#define ls(x) son[x][0]

#define rs(x) son[x][1]

int ans,fa[MAXN],val[MAXN],sum[MAXN],fak[MAXN],son[MAXN][2];



bool rnk (int x){return son[fa[x]][1] == x;}

bool isroot (int x){return son[fa[x]][rnk(x)] != x;}

void pushup (int x){sum[x] = sum[ls(x)] + sum[rs(x)] + val[x] + fak[x];}

void rotate (int x){

	int y = fa[x],z = fa[y],k = rnk(x),w = son[x][!k];

	if (!isroot (y)) son[z][rnk(y)] = x;son[x][!k] = y,son[y][k] = w;

	if (w) fa[w] = y;fa[y] = x,fa[x] = z;

	pushup (y),pushup (x);

}

void splay (int x){

	while (!isroot (x)){

		int y = fa[x];

		if (!isroot (y)) rotate (rnk(x) == rnk(y) ? y : x);

		rotate (x);

	}

}

int calc (int u,int t,int h){return rs(u) ? 2 * (t - h) : (val[u] * 2 > t ? 2 * (t - val[u]) : t - 1);}

void access (int x,int w){

	int t,h,tmp = x;

	for (Int y = 0;x;x = fa[y = x]){

		splay (x),t = sum[x] - sum[ls(x)],h = sum[rs(x)];

		ans -= calc (x,t,h),(x == tmp) ? (val[x] += w) : (fak[x] += w),sum[x] += w,t += w;

		if (h * 2 <= t) rs(x) = 0,fak[x] += h,h = (x == tmp ? h : 0);

		if (sum[y] * 2 > t) rs(x) = y,fak[x] -= sum[y],h = sum[y];

		ans += calc (x,t,h),pushup (x);

	}

}



void init (int u,int par){

	sum[u] = val[u];int p = 0,mx = val[u];

	for (Int v : g[u]) if (v ^ par){

		fa[v] = u,init (v,u),sum[u] += sum[v];

		if (sum[v] > mx) mx = sum[v],p = v;

	}

	ans += min (sum[u] - 1,2 * (sum[u] - mx));

	if (mx * 2 > sum[u]) rs(u) = p;

	fak[u] = sum[u] - sum[rs(u)] - val[u];

}



signed main(){

	read (n,m);

	for (Int i = 1;i <= n;++ i) read (val[i]);

	for (Int i = 2,u,v;i <= n;++ i) read (u,v),g[u].push_back (v),g[v].push_back (u);

	init (1,0),write (ans),putchar ('\n');

	while (m --> 0){

		int u,w;read (u,w);

		access (u,w),write (ans),putchar ('\n');

	}

	return 0;

}