如果是找中间点的话,只能做到
O
(
n
2
)
O(n^2)
O(n2),
显然两个点到一个点的距离等距是很好维护的,但是多一个点就不太行了.
既然这种情况麻烦,我们不妨把它记录在状态内,这样找第3个点就很方便啦~~
定义 f [ i ] [ j ] f[i][j] f[i][j]表示子树 i i i内深度为 j j j的点数, g [ i ] [ j ] g[i][j] g[i][j]表示子树 i i i内钦定第三个点在子树外且距离 i i i j j j个单位时,能够形成的合法三元组数量, l e n i len_i leni表示子树 i i i内最大深度.
转移和贡献如下:
首先贡献答案
a
n
s
+
=
∑
y
∑
i
=
1
l
e
n
y
g
[
x
]
[
i
]
∗
f
[
y
]
[
i
−
1
]
+
g
[
y
]
[
i
]
∗
f
[
x
]
[
i
−
1
]
ans +=\sum_y\sum_{i=1}^{len_y} g[x][i]*f[y][i-1]+g[y][i]*f[x][i-1]
ans+=∑y∑i=1lenyg[x][i]∗f[y][i−1]+g[y][i]∗f[x][i−1].(第三个点距离
y
y
y
i
i
i个单位且在子树
y
y
y外,那么显然距离
x
x
x就是
i
−
1
i-1
i−1啊…
然后考虑转移:
g
[
x
]
[
i
−
1
]
+
=
g
[
y
]
[
i
]
g[x][i-1]+=g[y][i]
g[x][i−1]+=g[y][i],
g
[
x
]
[
i
]
+
=
f
[
x
]
[
i
]
∗
f
[
y
]
[
i
−
1
]
g[x][i] += f[x][i]*f[y][i-1]
g[x][i]+=f[x][i]∗f[y][i−1],
f
[
x
]
[
i
]
+
=
f
[
y
]
[
i
−
1
]
f[x][i] += f[y][i-1]
f[x][i]+=f[y][i−1].
注意先贡献后转移,同时注意
g
g
g是往前贡献,所以我们长链剖分的时候记得开够空间.
int n,len[N],son[N],fa[N];
ll _f[N<<2],*p=_f,*f[N],*g[N],ans;
void New(int x) {
f[x]=p; p += 2*len[x];
g[x]=p; p += 2*len[x];
}
V<int> e[N];
void ins(int x,int y) {e[x].pb(y);}
void dfs(int x) {
for(int y:e[x]) if(y ^ fa[x]) {
fa[y]=x; dfs(y);
if(len[son[x]] < len[y]) son[x]=y;
}
len[x]=len[son[x]]+1;
}
void DFS(int x) {
f[x][0]=1;
if(son[x]) f[son[x]]=f[x]+1,g[son[x]]=g[x]-1,DFS(son[x]),ans += g[x][0];
else return ;
for(int y:e[x]) if(y ^ son[x] && y ^ fa[x]) {
New(y); DFS(y);
FOR(i,len[y]) ans += g[x][i]*f[y][i-1] + f[x][i-1]*g[y][i];
FOR(i,len[y]) g[x][i-1] += g[y][i], g[x][i] += f[x][i] * f[y][i-1], f[x][i] += f[y][i-1];
}
}
int main() {
qr(n); int x,y;
rep(i,2,n) qr(x),qr(y),ins(x,y),ins(y,x);
dfs(1); New(1); DFS(1); pr2(ans); return 0;
}