Loading

MX galaxy Day2/3

Luogu P4211

差分之后扫描线。
深度可以转化为到根路径节点个数。
\(lca\) 深度可以变成一个节点到根路径加,另一个节点到根路径求和。

点击查看

#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)

const int _ = 2e5 + 7;
const int mod = 201314;
typedef long long ll;

int n, m, ans[_];
int top[_], dep[_], fa[_], sz[_], son[_], dfn[_], idx;
int sum[_], tag[_];
std::vector <std::pair<int, int> > q[_];
std::vector <int> e[_];

void Dfs(int u) {
	sz[u] = 1, dep[u] = dep[fa[u]] + 1;
	for (int v : e[u]) {
		Dfs(v), sz[u] += sz[v];
		if (sz[v] > sz[son[u]]) son[u] = v;
	}
}
void Dfs(int u, int tp) {
	top[u] = tp, dfn[u] = ++idx;
	if (!son[u]) return; Dfs(son[u], tp);
	for (int v : e[u]) if (v != son[u]) Dfs(v, v);
}
#define ls p << 1
#define rs p << 1 | 1
#define mid (s + t >> 1)
void pu(int p) { sum[p] = (sum[ls] + sum[rs]) % mod; }
void upd(int p, int k, int s, int t) { sum[p] = (sum[p] + k * (t - s + 1) % mod) % mod, tag[p] = (tag[p] + k) % mod; }
void pd(int p, int s, int t) { if (tag[p]) upd(ls, tag[p], s, mid), upd(rs, tag[p], mid + 1, t), tag[p] = 0; }
void mdy(int l, int r, int s, int t, int p) {
	if (r < s or t < l) return;
	if (l <= s and t <= r) return upd(p, 1, s, t); pd(p, s, t);
	mdy(l, r, s, mid, ls), mdy(l, r, mid + 1, t, rs); pu(p);
}
int qry(int l, int r, int s, int t, int p) {
	if (r < s or t < l) return 0;
	if (l <= s and t <= r) return sum[p]; pd(p, s, t);
	return (qry(l, r, s, mid, ls) + qry(l, r, mid + 1, t, rs)) % mod;
}
#undef ls
#undef rs
#undef mid
void Mdy(int x) { while (x) mdy(dfn[top[x]], dfn[x], 1, n, 1), x = fa[top[x]]; }
int Qry(int x) { int res = 0;
	while (x) res = (res + qry(dfn[top[x]], dfn[x], 1, n, 1)) % mod, x = fa[top[x]];
	return res;
}

int main() {
	scanf("%d%d", & n, & m);
	lep(i, 2, n) scanf("%d", fa + i), ++fa[i], e[fa[i]].push_back(i);
	Dfs(1), Dfs(1, 1);

	int l, r, x;
	lep(i, 1, m) scanf("%d%d%d", & l, & r, & x), ++l, ++r, ++x,
		q[l - 1].push_back({x, -i}), q[r].push_back({x, i});

	lep(u, 1, n) {	Mdy(u);
		for (auto t : q[u]) { int x = t.first, i = t.second;
			if (i > 0) ans[i] = (ans[i] + Qry(x)) % mod;
			else ans[-i] = (ans[i] - Qry(x)) % mod;
		}
	}

	lep(i, 1, m) printf("%d\n", (ans[i] % mod + mod) % mod);
	return 0;
}


Luogu P6798

差分后考虑一段值域前缀 \([1, R]\)
假设到根路径上一个点子树内除去 \(a\) 后的最大值是 \(v\)
如果 \(v > R\) ,则贡献为 \(v\)
否则,贡献为 \(\frac{v^2-v+R^2+R}{2}\)
分界点可以二分,同时 \(v\) 的值一段是次大值,一段是最大值,同样二分分界点。
加上树上前缀和即可。

点击查看

#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)

const int _ = 5e5 + 7;
const int V = 20;
const int mod = 998244353;
const int Inv2 = (mod + 1) / 2;
typedef long long ll;

int n, q, opt, fa[_][V + 1], pos[_];
ll sum[_][2], sum_2[_][2], a[_], dep[_], ans, All, mx[_], se[_];
std::vector <int> e[_];

void Init(int u, int f) {
	dep[u] = dep[fa[u][0] = f] + 1, mx[u] = u;
	lep(i, 1, V) fa[u][i] = fa[fa[u][i - 1]][i - 1];
	for (int v : e[u]) if (v != f) {
		Init(v, u);
		if (a[mx[v]] > a[mx[u]]) se[u] = a[se[v]] > a[mx[u]] ? se[v] : mx[u], mx[u] = mx[v];
		else se[u] = a[mx[v]] > a[se[u]] ? mx[v] : se[u];
	}
}
void Init(int u) {
	All = (All + a[mx[u]]) % mod;
	sum[u][0] = (sum[fa[u][0]][0] + a[mx[u]]) % mod, sum[u][1] = (sum[fa[u][0]][1] + a[se[u]]) % mod;
	sum_2[u][0] = (sum_2[fa[u][0]][0] + a[mx[u]] * a[mx[u]] % mod) % mod,
	sum_2[u][1] = (sum_2[fa[u][0]][1] + a[se[u]] * a[se[u]] % mod) % mod;
	int p = u;
	rep(i, V, 0) if (a[mx[fa[p][i]]] == a[u] and a[se[fa[p][i]]] < a[u]) p = fa[p][i];
	pos[u] = a[se[u]] < a[u] ? fa[p][0] : u;
	for (int v : e[u]) if (v != fa[u][0]) Init(v);
}
ll S2(int u, int v, int o) { return (sum_2[v][o] - sum_2[u][o]) % mod; }
ll S(int u, int v, int o) { return (sum[v][o] - sum[u][o]) % mod; }
ll Get(ll R, int x) {
	ll res = (All - sum[x][0]) * R % mod;
	int l = x, r = pos[x];
	rep(i, V, 0) if (dep[fa[l][i]] > dep[r] and a[se[fa[l][i]]] < R) l = fa[l][i];
	
	if (r != x and a[se[l]] < R) res = (res + (S2(fa[l][0], x, 1) - S(fa[l][0], x, 1)) % mod * Inv2 % mod) % mod,
		res = (res + (dep[x] - dep[l] + 1) * (R * (R + 1) % mod) % mod * Inv2 % mod) % mod;
	if (r != x) { int q = a[se[l]] < R ? fa[l][0] : l;
		res = (res + R * S(r, q, 1) % mod) % mod;
	}
	int c = r;
	rep(i, V, 0) if (fa[c][i] and a[mx[fa[c][i]]] < R) c = fa[c][i];
	if (c and a[mx[c]] < R) res = (res + (S2(fa[c][0], r, 0) - S(fa[c][0], r, 0)) % mod * Inv2 % mod) % mod,
		res = (res + (dep[r] - dep[c] + 1) * (R * (R + 1) % mod) % mod * Inv2 % mod) % mod;
	c = c and a[mx[c]] < R ? fa[c][0] : c;
	res = (res + R * S(0, c, 0) % mod) % mod;
	return res;
}

int main() {
	scanf("%d%d%d", & n, & q, & opt);
	lep(i, 1, n) scanf("%lld", a + i); int u, v;
	lep(i, 2, n) scanf("%d%d", & u, & v),
		e[u].push_back(v), e[v].push_back(u);
	Init(1, 0), Init(1);

	int l, r, x;
	while (q--) {
		scanf("%d%d%d", & l, & r, & x);
		l = (l + opt * ans % n) % n + 1, r = (r + opt * ans % n) % n + 1, x = (x + opt * ans % n) % n + 1;
		if (l > r) std::swap(l, r);

		ans = (Get(r, x) - Get(l - 1, x)) % mod;
		ans = (ans + mod) % mod;
		printf("%lld\n", ans);
	}
	return 0;
}

CF1009F

考虑暴力 \(dp\) ,记 \(f[u, i]\)\(u\) 子树内距离 \(u\)\(i\) 的点的个数。
我们通过长链剖分和指针,让每个链头继承长儿子的信息,其他儿子暴力合并。
复杂度的话,每个点只会在链头被作为轻儿子合并一次,是 \(O(n)\) 的。
实现上,定义一个 \(O(n)\) 的数组,将首指针存下来,每次给链头分配相应高度的空间。

点击查看

#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)

const int _ = 1e6 + 7;
typedef long long ll;

int n, son[_], ans[_], hg[_], tl[_]; int* dp[_], *nw = tl;
std::vector <int> e[_];

void Init(int u, int f) {
	hg[u] = 1;
	for (int v : e[u]) if (v != f) {
		Init(v, u), hg[u] = std::max(hg[u], hg[v] + 1);
		if (hg[v] > hg[son[u]]) son[u] = v;
	}
}
void Dfs(int u, int f) {
	dp[u][0] = 1;
	if (!son[u]) return; 
	dp[son[u]] = dp[u] + 1, Dfs(son[u], u);
	ans[u] = ans[son[u]] + 1;
	for (int v : e[u]) if (v != f and v != son[u]) {
		dp[v] = nw, nw += hg[v], Dfs(v, u);
		lep(i, 1, hg[v]) {
			dp[u][i] += dp[v][i - 1];
			if (dp[u][i] > dp[u][ans[u]] or dp[u][i] == dp[u][ans[u]] and i < ans[u]) ans[u] = i;
		}
	}
	if (dp[u][ans[u]] == 1) ans[u] = 0;
}

int main() {
	scanf("%d", & n); int u, v;
	lep(i, 2, n) scanf("%d%d", & u, & v),
		e[u].push_back(v), e[v].push_back(u);
	Init(1, 0); 
	dp[1] = nw, nw += hg[1]; Dfs(1, 0);
	lep(i, 1, n) printf("%d\n", ans[i]);
	return 0;
}

Luogu P5903

对于每个链头,向上和向下各记录高度个点。
处理倍增数组,每次先跳到 \(x\)\(2^{\log k}\) 级祖先的链头,然后可以利用记录直接读取。
空间上,我们只记录了 \(O(n)\) 个点。
时间上是 \(O(n\log n) \sim O(1)\) 的。
正确性的话,链头到 \(fa_k\) 距离一定小于链长,否则倍增一定会往上再跳一次。

点击查看

#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)

const int _ = 5e5 + 7;
const int V = 21;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;

int lg[_], n, m, dep[_], hg[_], son[_], top[_], fa[_][V + 1], rt; u32 s; u64 ans, sum;
std::vector <int> e[_], g[_];

inline u32 get(u32 x) {
	x ^= x << 13;
	x ^= x >> 17;
	x ^= x << 5;
	return s = x; 
}
void Init(int u) {
	dep[u] = dep[fa[u][0]] + 1, hg[u] = 1;
	lep(i, 1, V) fa[u][i] = fa[fa[u][i - 1]][i - 1];
	for (int v : e[u]) {
		Init(v);
		hg[u] = std::max(hg[u], hg[v] + 1);
		if (hg[v] > hg[son[u]]) son[u] = v;
	}
}
void Init(int u, int tp) {
	top[u] = tp, g[tp].push_back(u);
	if (!son[u]) return;  Init(son[u], tp);
	for (int v : e[u]) if (v != son[u]) Init(v, v);
	if (u == tp) {
		int len = hg[u];
		while (len-- and fa[u][0]) g[tp].push_back(u = fa[u][0]);
	}
}
u32 qry(int x, int k) {
	if (!k) return x;
	int y = top[fa[x][lg[k]]];
	if (dep[x] - k >= dep[y]) return g[y][dep[x] - k - dep[y]];
	return g[y][dep[y] - dep[x] + k - 1 + hg[y]];
}

int main() {
	scanf("%d%d%u", & n, & m, & s);
	lep(i, 1, n) {
		scanf("%d", fa[i]); 
		if (fa[i][0]) e[fa[i][0]].push_back(i);
		else rt = i;
		if (i > 1) lg[i] = lg[i >> 1] + 1;
	}

	Init(rt), Init(rt, rt);

	lep(i, 1, m) {
		int x = (get(s) ^ sum) % n + 1, k = (get(s) ^ sum) % dep[x];
		sum = qry(x, k);
		ans ^= sum * i;
	}

	printf("%llu\n", ans);
	return 0;
}


qoj#9492. 树上简单求和

两棵树分别树剖,转变为区间问题。
相当于二维平面上 \(n\) 个散点,横坐标区间加,纵坐标区间求和,这个问题只能做到 \(O(\sqrt n)\) 复杂度。
分块,考虑散块和整块分开计算修改贡献。
散块,总共有 \(O(m\log n \sqrt n)\) 个修改,\(O(m\log n)\) 个查询。
使用值域分块做到 \(O(1)\) 修改 \(O(\sqrt n)\) 查询来平衡根号。
对于整块,按照时间顺序扫描,对另一维预处理 \(pos_i\) 表示每个坐标的前缀中有多少个位置被当前块所影响。
差分计算即可。
注意空间,要分别对每个整块处理,共用一份空间。
时间复杂度 \(O(n\sqrt n \log n)\) ,空间复杂度 \(O(n\log n)\)

点击查看

#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)

const int _ = 2e5 + 7;
typedef long long ll;
typedef unsigned long long u64;

struct qry { int l, r, id; };
int n, m, bel[_], st[500], ed[500], B, pos[_], t[_]; u64 a[_], ans[_], val[_], sum[500], K[_];
int X[_], Y[_];
std::vector <int> o;
std::vector <qry> ql, qr, q;

struct Graph{
	int ud[_], dfn[_], top[_], dep[_], son[_], sz[_], fa[_], idx;
	std::vector <int> e[_];
	void Input() { int u, v;
		lep(i, 2, n) scanf("%d%d", & u, & v),
			e[u].push_back(v), e[v].push_back(u);
	}
	void Dfs1(int u, int f) {
		sz[u] = 1, dep[u] = dep[fa[u] = f] + 1;
		for (int v : e[u]) if (v != f) {
			Dfs1(v, u), sz[u] += sz[v];
			if (sz[v] > sz[son[u]]) son[u] = v;
		}
	}
	void Dfs2(int u, int tp) {
		top[u] = tp, dfn[u] = ++idx, ud[idx] = u;
		if (!son[u]) return; Dfs2(son[u], tp);
		for (int v : e[u]) if (v != son[u] and v != fa[u]) Dfs2(v, v);
	}
	void Init() { Input(), Dfs1(1, 0), Dfs2(1, 1); }
	void Add(int x, u64 k) { val[x] += k, sum[bel[x]] += k; }
	void Calc(int l, int r, u64 k, int id) {
		ql.push_back({l, r, id});
		if (bel[l] == bel[r]) { lep(i, l, r) Add(t[i], k); return; }
		lep(i, l, ed[bel[l]]) Add(t[i], k);
		lep(i, st[bel[r]], r) Add(t[i], k);
	}
	void Calc(int l, int r, int id) {
		u64 res = 0; qr.push_back( { l, r, id } );
		if (bel[l] == bel[r]) { lep(i, l, r) res += val[i]; ans[id] += res; return; }
		lep(i, l, ed[bel[l]]) res += val[i];
		lep(i, bel[l] + 1, bel[r] - 1) res += sum[i];
		lep(i, st[bel[r]], r) res += val[i];
		ans[id] += res;
	}
	void Sca(int x, int y, u64 k, int id) {
		while (top[x] != top[y]) {
			if (dep[top[x]] < dep[top[y]]) std::swap(x, y);
			Calc(dfn[top[x]], dfn[x], k, id);
			x = fa[top[x]];
		}
		if (dep[x] > dep[y]) std::swap(x, y);
		Calc(dfn[x], dfn[y], k, id);
	}
	void Solve(int x, int y, int id) {
		while (top[x] != top[y]) {
			if (dep[top[x]] < dep[top[y]]) std::swap(x, y);
			Calc(dfn[top[x]], dfn[x], id);
			x = fa[top[x]];
		}
		if (dep[x] > dep[y]) std::swap(x, y);
		Calc(dfn[x], dfn[y], id);
	}
}T1, T2;

int main() {
	scanf("%d%d", & n, & m); B = std::sqrt(n);
	lep(i, 1, n) {
		scanf("%llu", a + i), bel[i] = (i - 1) / B + 1;
		if (!st[bel[i]]) st[bel[i]] = i; ed[bel[i]] = i;
	}

	T1.Init(), T2.Init();
	lep(i, 1, n) t[i] = T2.dfn[T1.ud[i]];
	lep(i, 1, n) T1.Sca(i, i, a[i], 0);


	int x, y; u64 k;
	lep(i, 1, m) {
		scanf("%d%d%llu", & x, & y, & k);
		X[i] = x, Y[i] = y, K[i] = k;
		T1.Sca(x, y, k, i), T2.Solve(x, y, i);
	}
	lep(i, 1, bel[n]) {
		lep(j, 1, n) x = T1.dfn[T2.ud[j]], pos[j] = pos[j - 1] + (st[i] <= x and x <= ed[i]);
		o.clear(), q.clear();
		for (auto t : ql) if (t.l < st[i] and ed[i] < t.r) o.push_back(t.id);
		for (auto t : qr) if (pos[t.r] - pos[t.l - 1]) q.push_back(t);
		int L = 0, R = 0; u64 k = 0;
		while (L < o.size() and R < q.size()) {
			if (o[L] <= q[R].id) k += K[o[L]], ++L;
			else ans[q[R].id] += (pos[q[R].r] - pos[q[R].l - 1]) * k, ++R;
		}
		while (R < q.size()) ans[q[R].id] += (pos[q[R].r] - pos[q[R].l - 1]) * k, ++R;
	}

	lep(i, 1, m) printf("%llu\n", ans[i]);
	return 0;
}


qoj#7855. 不跳棋

点分树上每个节点开一个桶下标为子树内节点到自己距离。
开一个全局桶维护经过每个分治中心的最短路径。
每个分治中心的最短路径通过最小值和次小值拼出,即使在同一棵子树内也没关系,因为最小答案一定是在那一棵公共子树内。
删点时暴力移动最/次小值和答案指针即可,因为只有 \(O(n\log n)\) 量级,移动到末尾就没有了。

点击查看

#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)

const int _ = 5e5 + 7;
const int V = 20;
typedef long long ll;

int n, tp, ans, idx; ll cnt[_], sum;
int dfn[_], dep[_], st[_][V + 1], fa[_], pr[_], lg[_];
int sz[_], tmp[_], rt; bool vis[_];
int pos[_][2];
std::vector <int> e[_], tot[_];

void Init(int u, int f) {
	dep[u] = dep[fa[u] = f] + 1, dfn[u] = ++idx, st[idx][0] = u;
	for (int v : e[u]) if (v != f) Init(v, u);
}
int upd(int x, int y) { return dep[x] < dep[y] ? x : y; }
void Init() {
	Init(1, 0);
	lep(j, 1, V) lep(i, 1, n - (1 << j) + 1)
		st[i][j] = upd(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
	lep(i, 2, n) lg[i] = lg[i >> 1] + 1;
}
int lca(int x, int y) {
	if (dfn[x] > dfn[y]) std::swap(x, y);
	int k = lg[dfn[y] - dfn[x] + 1], p = upd(st[dfn[x]][k], st[dfn[y] - (1 << k) + 1][k]);
	return x == p ? x : fa[p];
}
int len(int x, int y) { return dep[x] + dep[y] - 2 * dep[lca(x, y)]; }
void getrt(int u, int f, int total) {
	sz[u] = 1, tmp[u] = 0;
	for (int v : e[u]) if (!vis[v] and v != f)
		getrt(v, u, total), sz[u] += sz[v], tmp[u] = std::max(tmp[u], sz[v]);
	tmp[u] = std::max(tmp[u], total - sz[u]);
	if (!rt or tmp[u] < tmp[rt]) rt = u;
}
int Build(int u, int total) {
	rt = 0, getrt(u, 0, total), vis[rt] = true; int p = rt;
	tot[p].resize(total + 3);
	for (int v : e[rt]) if (!vis[v]) pr[Build(v, total - tmp[v])] = p;
	return p;
}
void Jump(int x) { int u = x; while (u) ++tot[u][len(u, x)], u = pr[u]; }
void Push(int p) {
	while (pos[p][0] < tot[p].size() - 1 and !tot[p][pos[p][0]]) ++pos[p][0];
	while (pos[p][1] < tot[p].size() - 1 and ((tot[p][pos[p][0]] == 1 and pos[p][0] == pos[p][1]) or !tot[p][pos[p][1]])) ++pos[p][1];
}
ll calc(int p) {
	if (pos[p][0] == pos[p][1]) return 1ll * tot[p][pos[p][0]] * (tot[p][pos[p][0]] - 1) / 2;
	return 1ll * tot[p][pos[p][0]] * tot[p][pos[p][1]];
}
void Del(int x) { 
	int u = x; 
	while (u) {
		cnt[pos[u][0] + pos[u][1]] -= calc(u), --tot[u][len(u, x)]; Push(u);
		cnt[pos[u][0] + pos[u][1]] += calc(u);
		while (!cnt[ans]) ++ans;
		u = pr[u];
	}
}

int main() {
	scanf("%d%d", & n, & tp); int u, v;
	lep(i, 2, n) scanf("%d%d", & u, & v),
		e[u].push_back(v), e[v].push_back(u);
	Init(), Build(1, n);
	lep(i, 1, n) Jump(i);
	lep(i, 1, n) Push(i), cnt[pos[i][0] + pos[i][1]] += calc(i);
	ans = 1;

	int x;
	rep(i, n - 2, 1) {
		scanf("%d", & x); x = (x ^ (tp * sum));
		Del(x);
		printf("%d %lld\n", ans, sum = cnt[ans]);
	}
	return 0;
}


qoj#9533. Classical Counting Problem

\([l, r]\) 的点全部加入,\(l\)\(r\) 所在的连通块就是以 \(l\) 为最小值, \(r\) 的最大值的树,这样的树如果存在,一定是唯一的。
假如权值是 \(min \times max\) ,考虑怎么做。
这样就变成了一个路径问题,要求两个端点分别是路径的最小值和最大值,点分治即可。
现在加上了一个 \(siz\) ,我们考虑 拆贡献。

记三元组 \((l, r, k)\) 表示 \(k\) 可以属于 \(l\)\(r\) 所确定的树内,每个三元组的贡献是 \(l \times r\)
依旧可以点分治,考虑跨过分治中心的答案。

(以下路径皆指代到分治中心的路径)
有三个候选部分 \(l\), \(r\), \(k\)
满足端点是路径最小值的加入 \(l\) ,端点是路径最大值的加入 \(r\) ,无论如何都要加入 \(k\)
每个部分存储一个二元组 \((mn, mx)\) 表示路径最小 / 大值。
一个合法的三元组满足以下条件:
\(lmn \le rmn\) , \(lmx \le rmx\) , \(lmn \le kmn\) , \(kmx \le rmx\)

\(lmx \le rmx\) , \(kmx \le rmx\) 用扫描线维护。
接下来只剩下 \(mn\) 之间的限制(下面在写法上省略)。
我们的问题是,目前所有的满足 \(l\le r\) \(\wedge\) \(l\le k\)\((l, k)\) 点对贡献 \(l\) ,我们要计数。
使用线段树维护值域,点对数量是可以 PushUp() 得到的,最后贡献再乘上 \(rmx\) 即可。

点击查看

#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)

const int _ = 2e5 + 7;
typedef long long ll;
typedef unsigned int u32;
typedef std::pair<int, int> PII;

int n;
struct SegTree {
	struct node { int l, k; u32 t; 
		node(int _l = 0, int _k = 0, u32 _t = 0) { l = _l, k = _k, t = _t; }
		friend node operator + (const node& x, const node& y) {
			return node(x.l + y.l, x.k + y.k, x.t + y.t + 1u * x.l * y.k);
		}
	}tr[_ << 2];
	int tot, ls[_ << 2], rs[_ << 2], rt;
	void Cls() { tot = rt = 0; }
	int frsh() { int p = ++tot; tr[p] = node(), ls[p] = rs[p] = 0; return p; }
#define mid ((s + t) >> 1)
	void pu(int p) { tr[p] = tr[ls[p]] + tr[rs[p]]; }
	void upd(int p, PII& v) { tr[p].l += v.first, tr[p].k += v.second; tr[p].t = 1ll * tr[p].l * tr[p].k; }
	void mdy(int d, int s, int t, PII& v, int &p) {
		if (!p) p = frsh();
		if (s == t) return upd(p, v);
		d <= mid ? mdy(d, s, mid, v, ls[p]) : mdy(d, mid + 1, t, v, rs[p]); pu(p);
	}
	void Mdy(int d, PII& v) { mdy(d, 1, n, v, rt); }
	node qry(int l, int r, int s, int t, int p) {
		if (!p or r < s or t < l) return node();
		if (l <= s and t <= r) return tr[p];
		return qry(l, r, s, mid, ls[p]) + qry(l, r, mid + 1, t, rs[p]);
	}
	u32 Qry(int l, int r) { return qry(l, r, 1, n, rt).t; }
#undef mid
}T;
int Test; u32 ans; PII l[_], r[_], k[_], nw; int cl, cr, ck;
int sz[_], tmp[_], rt; bool vis[_];
std::vector <int> e[_];

void getrt(int u, int f, int total) {
	sz[u] = 1, tmp[u] = 0;
	for (int v : e[u]) if (v != f and !vis[v])
		getrt(v, u, total), sz[u] += sz[v], tmp[u] = std::max(tmp[u], sz[v]);
	tmp[u] = std::max(tmp[u], total - sz[u]);
	if (!rt or tmp[u] < tmp[rt]) rt = u;
}
void dfs(int u, int f, int mn, int mx) {
	mn = std::min(mn, u), mx = std::max(mx, u);
	if (mn == u) l[++cl] = { mx, mn };
	if (mx == u) r[++cr] = { mx, mn };
	k[++ck] = { mx, mn };
	for (int v : e[u]) if (!vis[v] and v != f) dfs(v, u, mn, mx);
}
u32 calc(int u, int mn, int mx) {
	u32 res = 0; cl = cr = ck = 0; dfs(u, 0, mn, mx);
	int i = 1, j = 1;
	std::sort(l + 1, l + 1 + cl), std::sort(r + 1, r + 1 + cr), std::sort(k + 1, k + 1 + ck);
	T.Cls();
	lep(p, 1, cr) {
		while (i <= cl and l[i].first <= r[p].first) T.Mdy(l[i].second, nw = { l[i].second, 0 }), ++i;
		while (j <= ck and k[j].first <= r[p].first) T.Mdy(k[j++].second, nw = { 0, 1 });
		res += 1u * r[p].first * (T.Qry(1, n) - T.Qry(r[p].second + 1, n));
	}
	return res;
}
void Solve(int u, int total) {
	rt = 0, getrt(u, 0, total), ans += calc(rt, n + 1, 0), vis[rt] = true;
	for (int v : e[rt]) if (!vis[v]) ans -= calc(v, rt, rt); 
	for (int v : e[rt]) if (!vis[v]) Solve(v, total - tmp[v]);
}

int main() {
	scanf("%d", & Test);

	while (Test--) {
		scanf("%d", & n); int u, v;
		lep(i, 2, n) scanf("%d%d", & u, & v),
			e[u].push_back(v), e[v].push_back(u);

		Solve(1, n);
		printf("%u\n", ans); ans = 0;
		lep(i, 1, n) e[i].clear(), vis[i] = false;
	}

	return 0;
}


posted @ 2025-07-17 07:55  qkhm  阅读(25)  评论(0)    收藏  举报