正题
题目链接:https://www.luogu.com.cn/problem/P5904
题目大意
nnn个点的一棵树,求有多少个点对(i,j,k)(i,j,k)(i,j,k)使得这三个点距离相等。
解题思路
有两种情况,一是iii是j,kj,kj,k的祖先,二是i,j,ki,j,ki,j,k互相没有祖先关系
考虑dpdpdp,fi,jf_{i,j}fi,j表示iii点的子树中与iii距离为jjj的点的个数,然后gi,jg_{i,j}gi,j表示iii的子树中满足有多少个点(x,y)(x,y)(x,y)对使得dis(x,lca)=dis(y,lca)=dis(i,lca)+jdis(x,lca)=dis(y,lca)=dis(i,lca)+jdis(x,lca)=dis(y,lca)=dis(i,lca)+j。然后考虑统计答案
ans+=gy,i∗fx,i−1+gx,i+1∗fy,ians+=g_{y,i}*f_{x,i-1}+g_{x,i+1}*f_{y,i}ans+=gy,i∗fx,i−1+gx,i+1∗fy,i
然后考虑这个点对ggg的影响有
gx,i+1+=fx,i+1∗fy,ig_{x,i+1}+=f_{x,i+1}*f_{y,i}gx,i+1+=fx,i+1∗fy,i
然后继承gx,i−1+=gy,i,fx,i+1+=fy,ig_{x,i-1}+=g_{y,i},f_{x,i+1}+=f_{y,i}gx,i−1+=gy,i,fx,i+1+=fy,i
然后可以长链剖分进行优化,fff在长链上向后运动,但是ggg是向前运动的。
考虑结点111,因为每次向前运动要放在len1len_1len1的位置,但是该位置向后又要储存len1len_1len1个值,所以我们要开两倍的空间
时间复杂度O(n)O(n)O(n)
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=4e5+10;
struct node{ll to,next;
}a[N*2];
ll n,tot,ans,ls[N],son[N];
ll len[N],buff[N*2],bufg[N*2];
ll *f[N*2],*g[N*2],*nowf,*nowg;
void addl(ll x,ll y){a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot;return;
}
void dfs(ll x,ll fa){for(ll i=ls[x];i;i=a[i].next){ll y=a[i].to;if(y==fa)continue;dfs(y,x);if(len[y]>len[son[x]])son[x]=y;}len[x]=len[son[x]]+1;return;
}
void solve(ll x,ll fa){f[x][0]=1;if(son[x]){f[son[x]]=f[x]+1;g[son[x]]=g[x]-1;solve(son[x],x);}ans+=g[x][0];for(ll i=ls[x];i;i=a[i].next){ll y=a[i].to;if(y==son[x]||y==fa)continue;f[y]=nowf;nowf+=len[y];nowg+=len[y]*2+10;g[y]=nowg++;solve(y,x);for(ll j=0;j<len[y];j++){if(j)ans+=g[y][j]*f[x][j-1];ans+=g[x][j+1]*f[y][j];}for(ll j=0;j<len[y];j++){g[x][j+1]+=f[y][j]*f[x][j+1];if(j)g[x][j-1]+=g[y][j];f[x][j+1]+=f[y][j];}}return;
}
int main()
{scanf("%lld",&n);for(ll i=1;i<n;i++){ll x,y;scanf("%lld%lld",&x,&y);addl(x,y);addl(y,x);}dfs(1,1);nowf=buff;nowg=bufg;nowf+=len[1];nowg+=len[1]*2+10;f[1]=buff;g[1]=nowg++;solve(1,1);printf("%lld",ans);
}