【比賽題解】CSP2019 簡要題解


D1T1 code

簽到題,大家都會。

可以從高位往低位確定,如果遇到 \(1\),則將排名取反一下。

注意要開 unsigned long long

#include <bits/stdc++.h>

typedef unsigned long long u64; 

const int MaxN = 100; 

u64 n, K; 
bool ans[MaxN]; 

inline void solve(u64 dep, u64 k)
{
	if (dep == 0)
		return; 
	
	u64 lsze = 1ull << (dep - 1); 
	if (k < lsze)
	{
		ans[dep] = false; 
		solve(dep - 1, k); 
	}
	else
	{
		ans[dep] = true; 
		solve(dep - 1, lsze - (k - lsze) - 1); 
	}
}

int main()
{
    freopen("code.in", "r", stdin); 
    freopen("code.out", "w", stdout); 
    
	std::cin >> n >> K; 
	
	solve(n, K); 
	
	for (int i = n; i >= 1; --i)
		putchar(ans[i] ? '1' : '0'); 
	
	return 0; 
}

D1T2 brackets

簡單題,大家都會。

大家的做法都好巨,我只會奇奇怪怪的做法。

考慮每次加一個括號之后答案的增量,顯然只有加右括號的時候答案會增加。

我們記兩個量, \(lst\_lef_i\) 表示 \(i\to 1\) 的路徑上最后一個沒有匹配的左括號,\(lst\_blk_i\) 表示以 \(i\) 結尾的合法子串個數(本質上是數出類似這種串 ...(...)(...)(...)(...) 的極長合法后綴可以分成幾段 (...))。

這兩個量可以直接線性推出來,然后就做完了。時間復雜度 \(O(n)\)

#include <bits/stdc++.h>

template <class T>
inline void read(T &x)
{
	static char ch; 
	while (!isdigit(ch = getchar())); 
	x = ch - '0'; 
	while (isdigit(ch = getchar()))
		x = x * 10 + ch - '0'; 
}

typedef long long s64; 

const int MaxNV = 5e5 + 5; 
const int MaxNE = MaxNV; 

int n; 
int fa[MaxNV]; 
char s[MaxNV]; 

int lst_lef[MaxNV]; 
int lst_blk[MaxNV]; 

s64 ans[MaxNV], xor_ans; 

int main()
{
    freopen("brackets.in", "r", stdin); 
    freopen("brackets.out", "w", stdout); 
    
	scanf("%d%s", &n, s + 1); 
	for (int i = 2; i <= n; ++i)
		read(fa[i]); 
	
	if (s[1] == '(')
		lst_lef[1] = 1; 
	
	for (int u = 2; u <= n; ++u)
	{
		ans[u] = ans[fa[u]]; 
		
		if (s[u] == '(')
		{
			lst_lef[u] = u; 
			lst_blk[u] = 0; 
		}
		else
		{
			if (lst_lef[fa[u]])
			{
				int lef_u = lst_lef[fa[u]]; 
				
				lst_lef[u] = lst_lef[fa[lef_u]]; 
				lst_blk[u] = lst_blk[fa[lef_u]] + 1; 
				ans[u] += lst_blk[u]; 
			}
			else
			{
				lst_lef[u] = 0; 
				lst_blk[u] = 0; 
			}
        }
		xor_ans ^= 1LL * u * ans[u]; 
	}
	
	std::cout << xor_ans << std::endl; 
	
	return 0; 
}

D1T3 tree

細節題。這個題的 idea 挺好的,就是容易分類討論掛。

難度其實不大,放在 D1T3 其實沒啥毛病。可能出題人高估了我的代碼能力。我太菜了,考場上調不出來。

一個顯然的貪心就是從小到大枚舉數字,然后判斷這個數字最終能送到那個位置。顯然每次我們都貪心地選取最小的位置。

考慮一條路徑 \(u_1\to u_2\to \dots \to u_k\),假設我們要將 \(u_1\) 的原來數字送到 \(u_k\),那么需要滿足下列條件:

  • \((u_1,u_2)\) 是和 \(u_1\) 相連的所有邊中第一個被刪除的
  • \((u_{k - 1}, u_k)\) 是和 \(u_k\) 相連的所有邊中最后一個被刪除的
  • \((u_{i-1},u_i)\) 必須比 \((u_i,u_{i+1})\) 先刪除,並且在刪除 \((u_{i-1},u_i)\) 后,刪除 \((u_i,u_{i+1})\) 之前,不能有和 \(u_i\) 相連的其他邊被刪除。

那么我們就對每個點,維護出與其相連的所有邊的限制。這些限制具體可以用一個鏈表來表示,並且需要記錄每個點強制限制的第一個刪除的邊,和最后一個刪除的邊。

實現的時候,就從當前枚舉的數字所在的點開始 dfs,顯然滿足第一個和第三個條件的點構成一個聯通塊。我們只需要在 dfs 的時候順帶判斷這些條件能否滿足即可。

時間復雜度 \(O(n^2)\)。細節比較多,我是根據考場的混亂思路瞎寫的,相信讀者一定有比我更優秀的實現方法。

#include <bits/stdc++.h>

template <class T>
inline void read(T &x)
{
	static char ch; 
	while (!isdigit(ch = getchar())); 
	x = ch - '0'; 
	while (isdigit(ch = getchar()))
		x = x * 10 + ch - '0'; 
}

template <class T>
inline void putint(T x)
{
	static char buf[25], *tail = buf; 
	if (!x)
		putchar('0'); 
	else
	{
		for (; x; x /= 10) *++tail = x % 10 + '0'; 
		for (; tail != buf; --tail) putchar(*tail); 
	}
}

const int MaxN = 2e3 + 5; 

int n; 
int idx[MaxN], col[MaxN], fa[MaxN]; 
int adj[MaxN][MaxN], deg[MaxN]; 

int ans[MaxN]; 
int fir[MaxN], lst[MaxN]; 
int head[MaxN][MaxN], sze[MaxN][MaxN]; 
int pre[MaxN][MaxN], suf[MaxN][MaxN]; 

inline void init()
{
	read(n); 
	for (int i = 1; i <= n; ++i)
	{
		fir[i] = lst[i] = 0; 
		deg[i] = ans[i] = fa[i] = 0; 

		for (int j = 1; j <= n; ++j)
		{
			head[i][j] = j; 
			sze[i][j] = 1; 
			pre[i][j] = suf[i][j] = 0; 
		}
	}

	for (int i = 1; i <= n; ++i)
	{
		read(idx[i]); 
		col[idx[i]] = i; 
	}

	for (int i = 1; i < n; ++i)
	{
		int u, v; 
		read(u), read(v); 
		adj[u][++deg[u]] = v; 
		adj[v][++deg[v]] = u; 
	}
}

inline void dfs(int u, int src)
{
	if (u != src)
	{
		bool flg = true; 

		if (deg[u] != 1)
		{
			flg &= fir[u] != fa[u] && !suf[u][fa[u]]; 
			flg &= !lst[u] || lst[u] == fa[u]; 
			if (fir[u] && head[u][fir[u]] == head[u][fa[u]])
				flg &= sze[u][head[u][fa[u]]] == deg[u]; 
		}

		if (flg)
		{
			if (!ans[src] || u < ans[src])
				ans[src] = u; 
		}
	}
	for (int i = 1; i <= deg[u]; ++i)
	{
		int v = adj[u][i]; 
		if (v == fa[u])
			continue; 

		fa[v] = u; 

		bool flg = true; 
		if (u == src)
		{
			if (deg[u] != 1)
			{
				flg &= lst[u] != v && !pre[u][v]; 
				if (lst[u] && head[u][lst[u]] == head[u][v])
					flg &= sze[u][head[u][v]] == deg[u]; 
			}
		}
		else
		{
			flg &= !suf[u][fa[u]] || suf[u][fa[u]] == v; 
			flg &= !pre[u][v] || pre[u][v] == fa[u]; 
			flg &= suf[u][fa[u]] == v || head[u][v] != head[u][fa[u]]; 
			flg &= head[u][fir[u]] != head[u][v] && head[u][lst[u]] != head[u][fa[u]]; 
			if (head[u][lst[u]] == head[u][v] && head[u][fir[u]] == head[u][fa[u]])
				flg &= sze[u][head[u][lst[u]]] + sze[u][head[u][fir[u]]] == deg[u]; 
		}

		if (flg)
			dfs(v, src); 
	}
}

inline void modify(int x, int src)
{
	if (!x)
		return; 

	lst[x] = fa[x]; 

	int y = x; 
	while (fa[y] != src)
	{
		suf[fa[y]][fa[fa[y]]] = y; 
		pre[fa[y]][y] = fa[fa[y]]; 

		if (head[fa[y]][fa[fa[y]]] != head[fa[y]][y])
		{
			int l = head[fa[y]][fa[fa[y]]]; 
			int z = y; 
			while (z)
			{
				++sze[fa[y]][l]; 
				head[fa[y]][z] = l; 
				z = suf[fa[y]][z]; 
			}
		}
		y = fa[y]; 
	}

	fir[src] = y; 
}

inline void solve()
{
	for (int c = 1; c <= n; ++c)
	{
		int u = idx[c]; 
		if (n == 1)
		{
			puts("1"); 
			continue; 
		}

		fa[u] = 0; 
		dfs(u, u); 
		modify(ans[u], u); 

		putint(ans[u]); 
		putchar(" \n"[c == n]); 
	}
}

int main()
{
    freopen("tree.in", "r", stdin); 
    freopen("tree.out", "w", stdout); 
    
	int orzczk; 
	read(orzczk); 

	while (orzczk--)
	{
		init(); 
		solve(); 
	}

	return 0; 
}

D2T1 meal

簡單題,就我不會。考場上降智太嚴重了,會了 \(O(mn^3)\) 竟然不會 \(O(mn^2)\)。我校其他選手全部 AC 此題,水平高下立判。

因為如果有食材超過一半,那么最多只能有一個這樣的食材,所以不難想到用總的方案數減去有一個主要食材超過一半的方案數。總的方案數就是

\[\prod_{i=1}^n\left(1+\sum_{j=1}^ma_{i,j}\right)-1 \]

減一是因為不能一個都不選。

考慮如何限制某個食材超過一半,顯然我們可以考慮枚舉這個食材,然后把用這個食材的菜權值看成 \(+1\),不用這個食材的菜權值看成 \(-1\),那么相當於選的所有菜的總權值要大於 \(0\)

具體地,我們可以用一個背包 DP 實現。顯然大家都會,就不講了。

\(f(i,j)\) 表示前 \(i\) 種方法,選的菜的總權值為 \(j\) 的方案數。

假設現在限制的是第 \(p\) 種食材,那么轉移非常顯然:

\[\begin{aligned} f(i,j) &\leftarrow f(i-1,j-1)\times a_{i,p}\\ f(i,j) &\leftarrow f(i-1,j+1)\times \sum_{j \neq p}a_{i,j} \end{aligned} \]

為了避免負數下標,可以加一個常數。時間復雜度 \(O(mn^2)\)

#include <bits/stdc++.h>

template <class T>
inline void read(T &x)
{
	static char ch; 
	while (!isdigit(ch = getchar())); 
	x = ch - '0'; 
	while (isdigit(ch = getchar()))
		x = x * 10 + ch - '0'; 
}

const int MaxN = 1e2 + 5; 
const int MaxM = 2e3 + 5; 
const int mod = 998244353; 

int n, m; 
int a[MaxN][MaxM], sum[MaxN]; 

int f[MaxN][MaxN << 1]; 

inline void add(int &x, const int &y)
{
	x += y; 
	if (x >= mod)
		x -= mod; 
}

inline void dec(int &x, const int &y)
{
	x -= y; 
	if (x < 0)
		x += mod; 
}

inline int minus(int x, const int &y)
{
	x -= y; 
	return x < 0 ? x + mod : x; 
}

int main()
{
	freopen("meal.in", "r", stdin); 
	freopen("meal.out", "w", stdout); 

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

	int ans = 1; 
    for (int i = 1; i <= n; ++i)
        ans = 1LL * ans * (sum[i] + 1) % mod; 
    
    dec(ans, 1); 
	for (int p = 1; p <= m; ++p)
	{
		f[0][n] = 1; 
		for (int i = 1; i <= n; ++i)
			for (int j = 0; j <= (n << 1); ++j)
			{
				f[i][j] = f[i - 1][j]; 
				if (j > 0)
					add(f[i][j], 1LL * f[i - 1][j - 1] * a[i][p] % mod); 
				if (j < (n << 1))
					add(f[i][j], 1LL * f[i - 1][j + 1] * minus(sum[i], a[i][p]) % mod); 
			}

		for (int i = 1; i <= n; ++i)
			dec(ans, f[n][i + n]); 
	}

	printf("%d\n", ans); 

	return 0; 
}

D2T2 partition

打表找規律題,考場上來不及了。

開始我們有一個顯然的 DP 是,設 \(f(i,j)\) 表示前 \(i\) 個數,最后一段是 \([j+1,i]\),的最小平方和。這樣的 DP 實現地優秀一點可以做到 \(O(n^2)\)

強烈的感覺告訴我們,這題有奇妙結論,考場上當然是打表。打表后不難發現在合法范圍內,\(f(i,j)\)\(j\) 單調遞減

結論的證明參考出題人myy的博客: http://matthew99.blog.uoj.ac/blog/5299

簡單總結一下這個證明:

結論: 把所有解的斷點從大到小寫下來,然后剩下的位置補0,那么最優解對應的序列在所有位置都是最大值。(不難發現,這個定義使最優解唯一)

證明: 結論等價於,對於每個解,從后往前將每一段的和寫出來,然后補無限個零,得到一個對應的序列,那么最優解對應的序列任意位置的前綴和都是最小的。

假設這個對應從后往前寫出的每一段和的序列為 \(\{b_i\}\),考慮另一個解對應的序列 \(\{c_i\}\),顯然不會出現某個位置 \(k\) 的前綴和 \(\sum_{i=1}^kb_i > \sum_{i=1}^kc_i\),否則就會和最優解的定義矛盾。

因此現在需要證明的就是對於任意一個滿足

\[\forall k, \sum_{i=1}^kb_i\leq \sum_{i=1}^kc_i \]

的解對應序列 \(c\)\(c\)\(b\) 不同),都有

\[\sum_{i=1}^{+\infty}b_i^2 < \sum_{i=1}^{+\infty}c_i^2 \]

證明的思路是,將序列 \(c\) 經過一些使平方和減小的調整,並且使得任意時刻都滿足所有位置的前綴和不小於 \(b\),最后讓 \(c\) 變成 \(b\)。這樣就能證明 \(c\) 的平方和不小於 \(b\) 的。(注意調整的含義是直接對 \(c\) 進行修改,在調整過程中沒有必要保證 \(c\) 存在對應的原序列中的解,我們只關心 \(c\)\(b\) 平方和的大小關系)

注意到對於一個單調不增的序列 \(a\),若 \(i<j\)\(a_i>a_{i+1},a_{j-1}>a_j, a_i-a_j\geq 2\),將 \(a_i\) 減一,\(a_j\) 加一,可以使 \(a\) 仍然單調不增,並且平方和減小。

找到第一個滿足 \(c_u>b_u\) 的位置 \(u\),在 \(u\) 的后面一定能找到第一個位置 \(v\) 滿足 \(c_v<b_v\)(因為 \(c\)\(b\) 所有元素的總和一樣)。因為 \(c\)\(b\) 都單調不增,所以 \(c_u-c_v\geq 2\),即區間 \([u,v]\) 的權值跨度至少為 \(2\)。找到最小的滿足 \(i\geq u,c_{i}>c_{i+1}\) 的位置 \(i\),找到最大的滿足 \(j\leq v,c_{j-1}>c_j\) 的位置 \(j\),那么 \(u\leq i<j\leq v\),並且 \(c_i-c_j\geq 2\)。於是將 \(c_i\) 減一,\(c_j\) 加一,可以使 \(c\) 仍然遞增,並且平方和減小,不難發現,這么操作仍然保證了 \(c\) 的每個位置的前綴和不小於 \(b\) 的。

於是不斷這么操作,一定能使 \(c\) 最終變成 \(b\),而在操作過程中平方和不斷減小,於是原來的 \(c\) 的平方和是大於 \(b\) 的。\(\square\)

因此我們對於每個 \(i\) 只要記一個最優決策點即可,每個 \(i\) 的決策點一定是取在最靠右的合法位置。記這個位置為 \(p_i\)。考慮一個決策點 \(j(j<i)\) 是合法的當且僅當 \(s_i-s_j\geq s_j-s_{p_j}\),其中 \(s_i\) 表示 \(1\dots i\) 的前綴和。

移項一下這個條件就是 \(2s_j-s_{p_j} \leq s_i\),到這里可以用一個BIT 做到 \(O(n \log n)\)

但是實際上,不難發現 \(s_i\) 是單增的,也就是說一個決策點 \(j\) 對於當前的 \(i\) 是合法的,那么對於后面的肯定仍是合法的。於是我們考慮維護一個\(2s_j-s_{p_j}\) 單增的單調隊列。對於每個 \(i\) 就從隊頭開始找到最后一個合法決策點,再將 \(i\) 插到隊尾,並且將隊尾的那些 \(2s_j-s_{p_j}\geq 2s_i-s_{p_i}\) 的決策點彈掉。這樣的時間復雜度就是 \(O(n)\) 了。

本題還有個問題就是,你不能一邊做這個 DP 一邊算這個 DP 值,得把 \(p_i\) 存起來最后算(因為空間不夠)。高精還需要壓位/二進制。當然,你在 OJ 上用 __int128 也是可以的。

#include <bits/stdc++.h>

template <class T>
inline void read(T &x)
{
	static char ch; 
	static bool opt; 
	while (!isdigit(ch = getchar()) && ch != '-'); 
	x = (opt = ch == '-') ? 0 : ch - '0'; 
	while (isdigit(ch = getchar()))
		x = x * 10 + ch - '0'; 
	if (opt)
		x = ~x + 1; 
}

template <class T>
inline void putint(T x)
{
	static char buf[45], *tail = buf; 
	if (!x)
		putchar('0'); 
	else
	{
		if (x < 0)
		{
			putchar('-'); 
			x = ~x + 1; 
		}
		for (; x; x /= 10) *++tail = x % 10 + '0'; 
		for (; tail != buf; --tail) putchar(*tail); 
	}
}

typedef long long s64; 

const int MaxN = 4e7 + 5; 
const s64 mod = 1e9; 

int n, type, ql, qr; 
int que[MaxN], maxp[MaxN], b[MaxN]; 
s64 s[MaxN]; 

struct bignum
{
	int len; 
	s64 a[7]; 
	bignum(){}
	bignum(s64 t)
	{
		len = 1; 
		memset(a, 0, sizeof(a)); 

		a[1] = t % mod; 
		if (t >= mod)
			a[++len] = t / mod; 
	}

	inline void operator += (const bignum &rhs)
	{
		len = std::max(len, rhs.len); 
		for (int i = 1; i <= len; ++i)
		{
			a[i] += rhs.a[i]; 
			a[i + 1] += a[i] / mod; 
			a[i] %= mod; 
		}
		if (a[len + 1])
			++len; 
	}

	inline bignum operator * (const bignum &rhs) const
	{
		bignum res(0); 
		res.len = len + rhs.len; 
		for (int i = 1; i <= len; ++i)
			for (int j = 1; j <= rhs.len; ++j)
				res.a[i + j - 1] += a[i] * rhs.a[j]; 
		for (int i = 1; i < res.len; ++i)
		{
			res.a[i + 1] += res.a[i] / mod; 
			res.a[i] %= mod; 
		}
		while (res.len > 1 && !res.a[res.len])
			--res.len; 
		return res; 
	}

	inline void print()
	{
		printf("%d", (int)a[len]); 
		for (int i = len - 1; i >= 1; --i)
			printf("%09d", (int)a[i]); 
	}
}res(0); 

inline s64 calc(int x)
{
	return 2 * s[x] - s[maxp[x]]; 
}

int main()
{
	freopen("partition.in", "r", stdin); 
	freopen("partition.out", "w", stdout); 

	read(n), read(type); 
	if (type == 0)
	{
		for (int i = 1; i <= n; ++i)
		{
			int x; 
			read(x); 
			s[i] = s[i - 1] + x; 
		}
	}
	else
	{
		int x, y, z, m; 
		read(x), read(y), read(z), read(b[1]), read(b[2]), read(m); 
		for (int i = 1, lstp = 0; i <= m; ++i)
		{
			int p, l, r; 
			read(p), read(l), read(r); 
			for (int j = lstp + 1; j <= p; ++j)
			{
				if (j > 2)
					b[j] = (1LL * x * b[j - 1] + 1LL * y * b[j - 2] + z) % (1 << 30); 
				s[j] = s[j - 1] + b[j] % (r - l + 1) + l; 
			}
			lstp = p; 
		}
	}
	
	que[ql = qr = 1] = 0; 
	for (int i = 1; i <= n; ++i)
	{
		while (ql < qr && calc(que[ql + 1]) <= s[i])
			++ql; 
		maxp[i] = que[ql]; 
		while (ql <= qr && calc(que[qr]) >= calc(i))
			--qr; 
		que[++qr] = i; 
	}

	int cur = n; 
	while (cur)
	{ 
		res += bignum(s[cur] - s[maxp[cur]]) * bignum(s[cur] - s[maxp[cur]]); 
		cur = maxp[cur]; 
	}

	res.print(); 
	
	return 0; 
}

D2T3 centroid

不難題,就我不會。因為這是 D2T3 所以我只想着寫暴力,實際上這題挺簡單的。

將題目中的計算方式轉化為:對於每個點,計算它成為重心的方案數。

那么考慮一個點 \(u\),將其硬點為,對於它的每個兒子 \(v\),相當於在 \(v\) 的子樹再選出一個大小為 \(s_0\) 的子樹,這個 \(s_0\) 需要在一個范圍。具體地,我們記 \(s'\) 表示 \(u\) 除了 \(v\) 以外的其他兒子的最大子樹大小,記 \(s_v\) 表示 \(v\) 的子樹大小。

那么就有

\[s_v-s_0 \leq \left\lfloor\frac {n-s_0}2\right\rfloor \\ s_0 \geq 2s_v-n \]

以及

\[s^\prime\leq \left\lfloor \frac {n-s_0}2\right\rfloor\\ s_0\leq n-2s^\prime \]

那么就要求 \(s_0\in [2s_v-n,n-2s^\prime]\)

對應到原樹(欽定 \(1\) 為根的有根樹)上,這相當於分兩類討論:

  • \(v\)\(u\) 的一個兒子,這個時候直接在 dfs 時用一個 BIT 存下當前結點到根的路徑上所有點的詢問區間對應的貢獻,然后遍歷到一個點的時候在 BIT 上單點查詢即可。
  • \(v\)\(u\) 的父親,這時候的統計比較復雜,需要分成幾塊統計:
    • 原樹中不在 \(u\) 子樹中且不在 \(u\) 到根路徑上的結點的 \(size\in [2(n-s_u)-n,n-2s^\prime]\),這個可以用整棵樹\(size\) 在這個區間的點數減去子樹內 \(size\) 在這個區間的點數,再減去到根結點的路徑上的點 \(size\) 在這個區間的點數。子樹詢問可以歸到第一類的 BIT 中,到根結點的可以另外再維護一個 BIT。
    • 原樹中 \(u\) 到根的路徑上,向父親方向的「子樹」。這個可以 dfs 的時候歸到上面第二個 BIT 維護。

那么這樣我們就可以用 BIT 來做了。時間復雜度 \(O(n\log n)\),常數有點大,可能打不過線段樹選手。

(補充:我太菜了,其實那些詢問看成二維數點然后離線 BIT 就可以了,不用我這么麻煩,但是這樣常數好像也很大

#include <bits/stdc++.h>

template <class T>
inline void read(T &x)
{
	static char ch; 
	while (!isdigit(ch = getchar())); 
	x = ch - '0'; 
	while (isdigit(ch = getchar()))
		x = x * 10 + ch - '0'; 
}

template <class T>
inline void putint(T x)
{
	static char buf[25], *tail = buf; 
	if (!x)
		putchar('0'); 
	else
	{
		for (; x; x /= 10) *++tail = x % 10 + '0'; 
		for (; tail != buf; --tail) putchar(*tail); 
	}
}

template <class T>
inline void relax(T &x, const T &y)
{
	if (x < y)
		x = y; 
}

template <class T>
inline void tense(T &x, const T &y)
{
	if (x > y)
		x = y; 
}

typedef long long s64; 

const int MaxNV = 3e5 + 5; 
const int MaxNE = MaxNV << 1; 

int n; 
int ect, adj[MaxNV]; 
int nxt[MaxNE], to[MaxNE]; 

int fa[MaxNV], sze[MaxNV]; 
s64 ans, sum[MaxNV], bit_q[MaxNV], bit_u[MaxNV]; 

int max_sze[MaxNV][2]; 

#define trav(u) for (int e = adj[u], v; v = to[e], e; e = nxt[e])

inline void addEdge(int u, int v)
{
	nxt[++ect] = adj[u]; 
	adj[u] = ect; 
	to[ect] = v; 
}

inline void init()
{
	read(n); 

	ect = 0; 
	ans = 0; 
	for (int i = 1; i <= n; ++i)
	{
		adj[i] = sum[i] = 0; 
		bit_q[i] = bit_u[i] = 0; 
		max_sze[i][0] = max_sze[i][1] = 0; 
	}

	for (int i = 1; i < n; ++i)
	{
		int u, v; 
		read(u), read(v); 
		addEdge(u, v); 
		addEdge(v, u); 
	}
}

inline void bit_modify(int x, int val, s64 *bit)
{
	for (; x <= n; x += x & -x)
		bit[x] += val; 
}

inline s64 bit_query(int x, s64 *bit)
{
	s64 res = 0; 
	for (; x; x ^= x & -x)
		res += bit[x]; 
	return res; 
}

inline void seg_modify(int l, int r, int val, s64 *bit)
{
	relax(l, 1), tense(r, n); 
	if (r < l) return; 

	bit_modify(l, val, bit); 
	bit_modify(r + 1, -val, bit); 
}

inline s64 seg_query(int l, int r, s64 *bit)
{
	relax(l, 1), tense(r, n); 
	if (r < l) return 0; 
	return bit_query(r, bit) - bit_query(l - 1, bit); 
}

inline void upt(int x, int s)
{
	if (s >= max_sze[x][0])
	{
		max_sze[x][1] = max_sze[x][0]; 
		max_sze[x][0] = s; 
	}
	else
		relax(max_sze[x][1], s); 
}

inline int max_else(int x, int s)
{
	if (s == max_sze[x][0])
		return max_sze[x][1]; 
	return max_sze[x][0]; 
}

inline void dfs_init(int u)
{
	sze[u] = 1; 
	trav(u)
		if (v != fa[u])
		{
			fa[v] = u; 
			dfs_init(v); 

			sze[u] += sze[v]; 
			upt(u, sze[v]); 
		}
	upt(u, n - sze[u]); 
}

inline void dfs(int u)
{
	int ul = std::max(1, 2 * (n - sze[u]) - n); 
	int ur = std::min(n, n - 2 * max_else(u, n - sze[u])); 
	if (ul <= ur)
		ans += (sum[ur] - sum[ul - 1]) * u; 
	seg_modify(ul, ur, -u, bit_q); 
	ans += bit_query(sze[u], bit_q); 
	ans += seg_query(ul, ur, bit_u) * u; 

	trav(u)
		if (v != fa[u])
		{
			int t_s = max_else(u, sze[v]); 
			int l = 2 * sze[v] - n, r = n - 2 * t_s; 
			
			seg_modify(l, r, u, bit_q); 
			bit_modify(sze[u], -1, bit_u); 
			bit_modify(n - sze[v], 1, bit_u); 

			dfs(v); 

			seg_modify(l, r, -u, bit_q);
			bit_modify(sze[u], 1, bit_u); 
			bit_modify(n - sze[v], -1, bit_u);  
		}

	seg_modify(ul, ur, u, bit_q); 
}

inline void solve()
{
	dfs_init(1); 

	for (int i = 1; i <= n; ++i)
		++sum[sze[i]]; 
	for (int i = 1; i <= n; ++i)
		sum[i] += sum[i - 1]; 

	dfs(1); 

	putint(ans); 
	putchar('\n'); 
}

int main()
{
	freopen("centroid.in", "r", stdin); 
	freopen("centroid.out", "w", stdout); 

	int orzczk; 
	read(orzczk); 
	while (orzczk--)
	{
		init(); 
		solve(); 

	}
	return 0; 
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM