題目的難度顏色使用 Luogu 上的分級,由簡單到困難分別為 🔴🟠🟡🟢🔵🟣⚫。
Problem Statement
題目簡述
給一棵有 N 個點的樹,點號為 0,1,…,N−1。
對任意一對端點 (i,j),令 f(i,j) 為路徑 i→j 沒有包含到的最小點號;若這條路徑包含了所有點,則令 f(i,j)=N。
請求出所有 0≤i≤j<N 的 f(i,j) 總和。
Constraints
約束條件
- 2≤N≤2×105
- 0≤ui<vi<N
- 圖是一棵樹
- 輸入皆為整數
思路:貢獻轉換與鏈的動態維護
核心想法:不要直接算 mex 等於多少,而是算 mex 至少多少
對一條路徑而言,假設它的 mex 是 m,這意味著:
- 路徑上包含 0,1,…,m−1
- 路徑上不包含 m
這條路徑對答案的貢獻是 m。我們可以將貢獻 m 拆解為:
m=x=1∑N[m≥x]
根據前面的拆解,所有路徑的 mex 總和可以寫成:
路徑∑m=路徑∑x=1∑N[m≥x]
交換加總順序後得到:
x=1∑N(路徑∑[m≥x])
而「mex≥x」等價於「這條路徑包含所有點 0,1,…,x−1」。
因此,若令 g(x) 為「包含所有點 0,1,…,x−1 的路徑數量」,那麼所有路徑的 mex 總和即為:
x=1∑Ng(x)
這將問題轉化為:從小到大依序加入點,並動態維護「必須包含這些點的路徑數量」。
基礎:包含根節點的路徑數
以點 0 為根。整棵樹的路徑總數為 2N(N+1),而完全落在某個子樹內的路徑必定不經過根節點。若某子樹大小為 s,其內部路徑數為 2s(s+1)。
因此,包含點 0 的路徑數為:
g(1)=2N(N+1)−c∑2sc(sc+1)
維護必要點構成的鏈
要求路徑同時包含點 0,1,…,x−1 時,這些點在樹上的最小連通子圖必須是一條鏈,否則無任何簡單路徑能覆蓋。初始時鏈僅含點 0,兩端點重合。
依序加入點 x,有兩種情形:
情況一:新點已在鏈上
之前延伸其他點時已順便將其納入,鏈不變,g(x+1)=g(x)。
情況二:新點不在鏈上
從新點向父節點方向走,直到首次碰到鏈上的點。
第一次碰到的點,就是新點接入目前鏈的位置。
- 接入點是鏈的端點:可以將鏈往外延長至新點,更新端點即可。
- 接入點是鏈的內部點:這會在該點產生分叉,使得最小連通子圖變成度數為 3 的星狀結構。這意味著之後不可能存在任何簡單路徑能同時包含這些點,此時 g(x+1)=g(x+2)=⋯=g(N)=0,可以直接結束計算。
計算包含當前鏈的路徑數
鏈成功延伸後(或確定不變),需要計算涵蓋當前鏈的路徑數量。
一條路徑若要覆蓋整條鏈,其兩端必須分別落在鏈的兩側之外:
- 非根端點:該側必須以端點的子樹內某點結束,可選點數即為端點的子樹大小。
- 根端點(點 0):該側不能落在已被鏈佔用方向的子樹內。因此每當鏈從根向外延伸時,直接將根的「子樹大小」扣除該方向子樹的大小,根的剩餘大小即為該側可選點數。
直接修改根節點的子樹大小,讓它由「N」逐漸變成「根側剩餘可選點數」。此後無論哪端是根節點,兩端子樹大小的乘積就是當前涵蓋鏈的路徑總數,不需要額外變數維護根側狀態。
複雜度分析
- 時間複雜度:O(N)
- 空間複雜度:O(N)
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| import sys
sys.setrecursionlimit(int(3e5))
def main(): n = int(input()) g = [[] for _ in range(n)]
for _ in range(n - 1): u, v = map(int, input().split()) g[u].append(v) g[v].append(u)
pa = [-1] * n sz = [1] * n
def dfs(u, fa): pa[u] = fa for v in g[u]: if v == fa: continue dfs(v, u) sz[u] += sz[v]
dfs(0, -1)
ans = n * (n + 1) // 2 for v in g[0]: ans -= sz[v] * (sz[v] + 1) // 2
vis = [False] * n vis[0] = True
L = R = 0 for x in range(1, n): if vis[x]: ans += sz[L] * sz[R] continue
vis[x] = True v = x while not vis[pa[v]]: vis[pa[v]] = True v = pa[v] y = pa[v]
if y != L and y != R: break
if y == L: L = x else: R = x
if y == 0: sz[0] -= sz[v]
ans += sz[L] * sz[R]
print(ans)
if __name__ == "__main__": main()
|