[ABC337G] Tree Inversion(換根 dp + 值域線段樹)

Zhang_Wenjie發表於2024-10-21

link

題目形式就很換根 dp

如果這種題用樸素的做法求,就是暴力以每個點都做一次根跑樹,自底向上統計,時間是 \(O(n^2)\)

而換根 dp 的思想就是分兩步,

一般先欽定某個點(如 1)為根,統計一遍以 1 為根時的結果,

然後挖掘如果以其他點為根時,變換對結果的影響,一般就是自頂向下更新如果換根後的資訊

這樣分成兩次 dfs,時間就是線性的 \(O(n)\)


這個題發現就是求類似樹上逆序對的個數,即在同一個子樹裡,節點編號和深度大小關係相反

考慮第一步,設 \(f[x]\) 表示以節點編號 \(x\) 為根的子樹包含逆序對個數

顯然,我們要知道子樹中編號小於 \(x\) 的節點數量 \(a_x\),那麼轉移就很顯然了:

\[f[x] = a_x + \sum\limits_{y\in son(x)}f[y] \]


考慮第二步,設 \(g[x]\) 表示以 \(x\) 為根節點的樹的逆序對個數

思考由 \(x\) 換根為 \(y\) 後,\(g[x]\) 透過怎樣的轉換關係得到 \(g[y]\)

image

這裡還需要增加一個 \(b_x\) 表示以 \(x\) 為根的子樹內編號小於 \(fa[x]\) 的節點數量

需要注意的是,總的小於 \(y\) 的節點個數是 \(y - 1\),已知 \(y\) 子樹內的數量有 \(a_y\),相減即子樹外的部分

\[g[y] = g[x] - b_y + (y - 1 - a_y) \]


對於 \(a_x\)\(a_y\) 的求解

也是很重要的一部分,這裡是很經典的建值域線段樹,或是樹狀陣列,只不過這題是在樹上

要求子樹 \(x\) 內的值,可以作差一下,用全域性的值減掉子樹外的值即可,對應的就是遞迴完子樹和遞迴子樹前

時間是 \(O(n\log n)\)

#include <bits/stdc++.h>
#define re register int 
#define lp p << 1
#define rp p << 1 | 1
#define int long long

using namespace std;
const int N = 2e5 + 10;

struct Edge
{
	int to, next;
}e[N << 1];
int idx, h[N];
struct Tree
{
	int l, r, sum;
}t[N << 2];
int n, f[N], g[N], a[N], b[N];

inline void add(int x, int y)
{
	e[++ idx] = (Edge){y, h[x]};
	h[x] = idx;
}

inline void build(int p, int l, int r)
{
	t[p].l = l, t[p].r = r;
	if (l == r) return;
	int mid = (l + r) >> 1;
	build(lp, l, mid);
	build(rp, mid + 1, r);
}

inline void update(int p, int x, int k)
{
	if (t[p].l == x && t[p].r == x)
	{
		t[p].sum += k;
		return;
	}
	int mid = (t[p].l + t[p].r) >> 1;
	if (x <= mid) update(lp, x, k);
	if (x > mid) update(rp, x, k);
	t[p].sum = t[lp].sum + t[rp].sum;
}

inline int query(int p, int l, int r)
{
	if (l > r) return 0;
	if (l <= t[p].l && t[p].r <= r) return t[p].sum;
	int res = 0;
	int mid = (t[p].l + t[p].r) >> 1;
	if (l <= mid) res += query(lp, l, r);
	if (r > mid) res += query(rp, l, r);
	
	return res;	
}

void dfs1(int u, int fa)
{
	a[u] = -query(1, 1, u - 1);
	b[u] = -query(1, 1, fa - 1);
	
	for (re i = h[u]; i; i = e[i].next) 
	{
		int v = e[i].to;
		if (v == fa) continue;
		dfs1(v, u);
	}
	update(1, u, 1);
	a[u] += query(1, 1, u - 1);
	b[u] += query(1, 1, fa - 1);
	
	f[u] = a[u];
	for (re i = h[u]; i; i = e[i].next)
	{
		int v = e[i].to;
		if (v == fa) continue;
		f[u] += f[v]; 
	}
}

void dfs2(int u, int fa)
{
	if (u != 1)
		g[u] = g[fa] - b[u] + (u - 1 - a[u]);
	for (re i = h[u]; i; i = e[i].next)
	{
		int v = e[i].to;
		if (v == fa) continue;
		dfs2(v, u);
	}
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	
	cin >> n;
	for (re i = 1; i < n; i ++)
	{
		int x, y; cin >> x >> y;
		add(x, y), add(y, x);
	}
	build(1, 1, n);
	dfs1(1, 0);
	g[1] = f[1];
	dfs2(1, 0);
	for (re i = 1; i <= n; i ++) cout << g[i] << ' '; 
	cout << '\n';
	
	return 0;
}

相關文章