题目大意:
题目链接:https://www.luogu.org/problem/P2634
求树上任意两点之间的距离为3的倍数的概率。
思路:
设
f
[
x
]
[
0
/
1
/
2
]
f[x][0/1/2]
f[x][0/1/2]表示以
x
x
x为根的子树中,到达
x
x
x的所有路径中长度取模
3
3
3的余数为
0
/
1
/
2
0/1/2
0/1/2的数量。
那么如果一条边
(
x
,
y
)
(x,y)
(x,y)的长度为
d
d
d,则有
f
[
x
]
[
(
j
+
d
)
%
3
]
=
∑
f
[
y
]
[
j
]
∣
(
x
,
y
)
=
d
f[x][(j+d)\%3]=\sum f[y][j]\ |\ (x,y)=d
f[x][(j+d)%3]=∑f[y][j] ∣ (x,y)=d
那么如何求答案呢?
像数的直径一样,我们在加入
(
x
,
y
)
(x,y)
(x,y)这条边的贡献之前,先把
y
y
y和
x
x
x节点的其他子树的贡献计算出来。
也就是说,枚举
x
x
x到
y
y
y子树的长度
j
j
j,然后我们就要在
x
x
x的其他子树内找长度为
3
−
j
3-j
3−j的边,把这两条边数相乘即可。
那么由于
(
3
−
j
−
d
)
%
3
(3-j-d)\%3
(3−j−d)%3可能是负数,所以我们不妨将其写为
(
3
k
−
j
−
d
)
%
3
(3k-j-d)\%3
(3k−j−d)%3,其中
k
k
k为任意常数。只要
k
k
k足够大,那么原式结果不会变且一定是正数。
注意点对无序,所以最终
a
n
s
ans
ans要乘2。然后所有形如
(
x
,
x
)
(x,x)
(x,x)的点对我们都没计算,所以最终的边数就是
2
a
n
s
+
n
2ans+n
2ans+n。
时间复杂度
O
(
n
)
O(n)
O(n)
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=20010;
int n,ans,tot,head[N],f[N][3];
struct edge
{
int next,to,dis;
}e[N*2];
void add(int from,int to,int dis)
{
e[++tot].to=to;
e[tot].dis=dis;
e[tot].next=head[from];
head[from]=tot;
}
void dfs(int x,int fa)
{
f[x][0]++;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=fa)
{
dfs(v,x);
for (int j=0;j<=2;j++)
ans+=f[v][(999999999-j-e[i].dis)%3]*f[x][j];
for (int j=0;j<=2;j++)
f[x][(j+e[i].dis)%3]+=f[v][j];
}
}
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d",&n);
for (int i=1,x,y,z;i<n;i++)
{
scanf("%d%d%d",&x,&y,&z);
add(x,y,z); add(y,x,z);
}
dfs(1,0);
int GCD=__gcd(ans*2+n,n*n);
printf("%d/%d",(ans*2+n)/GCD,n*n/GCD);
return 0;
}