【洛谷P2634】聪聪可可【树形dp】
题目大意:
题目链接:https://www.luogu.org/problem/P2634
求树上任意两点之间的距离为3的倍数的概率。
思路:
设表示以为根的子树中,到达的所有路径中长度取模的余数为的数量。
那么如果一条边的长度为,则有
那么如何求答案呢?
像数的直径一样,我们在加入这条边的贡献之前,先把和节点的其他子树的贡献计算出来。
也就是说,枚举到子树的长度,然后我们就要在的其他子树内找长度为的边,把这两条边数相乘即可。
那么由于可能是负数,所以我们不妨将其写为,其中为任意常数。只要足够大,那么原式结果不会变且一定是正数。
注意点对无序,所以最终要乘2。然后所有形如的点对我们都没计算,所以最终的边数就是。
时间复杂度
代码:
#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;
}