题目
一棵树,找到三个点他们两两之间距离相同
题解
O ( N 2 ) O(N^2) O(N2)
定义f(u,d)表示以u为根的子树中,与u距离为d的节点数
g(u,d)表示以i为根的子树中,已经找到了两个点,如果在u子树外找到一个距u为d的点就能成为一个合法方案的这两个点的无序点对数
可有转移方程
f
[
u
]
[
i
]
+
=
f
[
v
]
[
i
−
1
]
f[u][i]+=f[v][i-1]
f[u][i]+=f[v][i−1]
g
[
u
]
[
i
−
1
]
+
=
g
[
v
]
[
i
]
g[u][i-1]+=g[v][i]
g[u][i−1]+=g[v][i]
a
n
s
+
=
f
[
u
]
[
i
−
1
]
∗
f
[
v
]
[
i
]
ans+=f[u][i-1]*f[v][i]
ans+=f[u][i−1]∗f[v][i]
g
[
u
]
[
j
+
1
]
+
=
f
[
u
]
[
j
+
1
]
∗
f
[
v
]
[
j
]
g[u][j+1]+=f[u][j+1]*f[v][j]
g[u][j+1]+=f[u][j+1]∗f[v][j]
O ( N ) O(N) O(N)
假如一个点只有一个儿子,就相当于
f
[
u
]
[
i
]
=
f
[
v
]
[
i
−
1
]
,
g
[
u
]
[
i
−
1
]
=
g
[
u
]
[
i
]
f[u][i]=f[v][i-1],g[u][i-1]=g[u][i]
f[u][i]=f[v][i−1],g[u][i−1]=g[u][i],似乎就只是数组移了个位
那么我们似乎就可以用启发式合并的思想,每次找到最长的一个链通过移位得到,而其他的轻边的贡献就暴力合并。
然而这玩意似乎是
O
(
n
)
O(n)
O(n)的?似乎是轻边长度和就是复杂度?
至于移位,我们可以用链表实现,这样重链的转移就是O(1)的了
这玩意好像叫长链剖分来着?
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=200010;
struct node{
int u,v,nxt;
}edge[N*2];
int head[N],mcnt;
void add_edge(int u,int v){
mcnt++;
edge[mcnt].u=u;
edge[mcnt].v=v;
edge[mcnt].nxt=head[u];
head[u]=mcnt;
}
int mx[N],son[N],fa[N];
void dfs1(int u){
mx[u]=0;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v;
if(v==fa[u])
continue ;
fa[v]=u;
dfs1(v);
mx[u]=max(mx[u],mx[v]+1);
if(mx[v]>mx[son[u]])
son[u]=v;
}
}
ll p[N*50];
ll *f[N],*g[N],*cnt=&p[0];
ll ans;
void dfs2(int u){
if(son[u]){
f[son[u]]=f[u]+1;
g[son[u]]=g[u]-1;
dfs2(son[u]);
}
f[u][0]=1;
ans+=g[u][0];
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v;
if(v==fa[u]||v==son[u])
continue ;
f[v]=++cnt;
cnt+=2*mx[v]+1;
g[v]=++cnt;
cnt+=2*mx[v]+1;
dfs2(v);
for(int j=mx[v];j>=0;j--){
if(j)
ans+=f[u][j-1]*g[v][j];
ans+=g[u][j+1]*f[v][j];
g[u][j+1]+=f[u][j+1]*f[v][j];
}
for(int j=0;j<=mx[v];j++){
if(j)
g[u][j-1]+=g[v][j];
f[u][j+1]+=f[v][j];
}
}
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs1(1);
f[1]=++cnt;
cnt+=2*mx[1]+1;
g[1]=++cnt;
cnt+=2*mx[1]+1;
dfs2(1);
printf("%lld\n",ans);
}