题目链接;https://www.luogu.com.cn/problem/P2634
题目大意:
思路:树形dp的题解:https://blog.csdn.net/qq_21433411/article/details/90180495
如果我们用点分治。换根统计之前的子树dp[i]表示之前子树路径%3=i的路径条数。tem[i]目前这棵树路径%3=i的路径条数;然后组合一下就可以了。
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=20005, inf=1e9+1000;
struct edg{
int to, w, next;
}e[N*4];
int tot, root, allnode, n, m;
LL ans=0;
LL dp[3]={0};
LL tem[3]={0};
int head[N], vis[N], siz[N], d[N];
int deep[N];//路径长度//deep[0]子节点个数
int f[N];//求重心的最多子节点个数
int q[N];
void add(int u, int v, int w){
e[tot].to=v, e[tot].next=head[u];
e[tot].w=w, head[u]=tot++;
}
void getroot(int u, int fa){//求重心
siz[u]=1;
f[u]=0;
for(int i=head[u]; i!=-1; i=e[i].next){
int to=e[i].to;
if(to==fa||vis[to]){
continue;
}
getroot(to, u);
siz[u]+=siz[to];
f[u]=max(f[u], siz[to]);
}
f[u]=max(allnode-siz[u], f[u]);
if(f[u]<f[root]){
root=u;
}
}
void getdeep(int u, int fa){//获取子树所有节点与根的距离
tem[d[u]%3]++, d[u]%=3;
for(int i=head[u]; i!=-1; i=e[i].next){
int to=e[i].to;
if(to==fa||vis[to]){
continue;
}
d[to]=d[u]+e[i].w;
d[to]%=3;
getdeep(to, u);
}
}
void cal(int u){//计算当前以重心x的子树下,所有情况的答案
d[u]=0;
dp[0]=1;
for(int i=head[u]; i!=-1; i=e[i].next){
int to=e[i].to;
if(vis[to]){
continue;
}
d[to]=e[i].w;
getdeep(to, u);
for(int i=0; i<3; i++){
for(int j=0; j<3; j++){
if((i+j)%3==0){
ans+=dp[i]*tem[j];
}
}
}
for(int i=0; i<3; i++){
dp[i]+=tem[i];
}
memset(tem, 0, sizeof(tem));
}
memset(dp, 0, sizeof(dp));
}
void work(int u){//以x为重心进行计算
vis[u]=1;
cal(u);
for(int i=head[u]; i!=-1; i=e[i].next){
int to=e[i].to;
if(vis[to]){
continue;
}
allnode=siz[to];//继续分治
root=0;
getroot(to, u);
work(root);
}
}
int main(){
int u, v, w;
while(~scanf("%d", &n)){
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
tot=1;
for(int i=1; i<=n-1; i++){
scanf("%d%d%d", &u, &v, &w);
add(u, v ,w), add(v, u, w);
}
root=0;
allnode=n, f[0]=inf;
getroot(1, 0);
work(root);
ans*=2;
ans+=n;
LL ANS=(n*n);
LL gcd=__gcd(ANS, ans);
ans/=gcd, ANS/=gcd;
printf("%lld/%lld\n", ans, ANS);
}
return 0;
}