【题解】聪聪可可

题目戳我

\(\text{Solution:}\)

显然题目所求和“大规模处理树上路径问题”这一特点相符。考虑点分治。

由于题目只要求对于\(3\)的倍数,所以我们可以分别记录\(tmp[i]\)表示到当前点路径长度为\(i\)的路径数目。\(i\in \text{{0,1,2}}\)

若我们知道了这三个量,则此处的答案就是\(tmp[0]^2+2*tmp[1]*tmp[2].\)

\(tmp[1]*tmp[2]\)之所以要乘以\(2\)是因为对于点对\((u,v),(v,u)\)它们算作两种。

\(tmp[0]^2\)之所以不需要乘以\(2,\)是因为它本身就是一个集合的自我组合。也就是说在平方的过程中,我们已经把上述情况考虑过了。

\(tmp[0]^2\)的组合意义就是从\(tmp[0]\)中任选两个点(注意选择点可以重合)相组合。而\(tmp[1]*tmp[2]\)虽然也是这个意思,但由于它们分别属于两个不同集合,所以我们最后计数需要把它们乘以\(2.\)

剩下的就是点分治模板了。找重心,计数的时候容斥一下,最后写个\(gcd\)就过了。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=100010;
inline int gcd(int x,int y){return !y?x:gcd(y,x%y);}
int tot,head[MAXN],siz[MAXN],Siz,ans,n;
struct edge{int nxt,to,dis;}e[MAXN];
inline void link(int x,int y,int w){e[++tot].to=y;e[tot].nxt=head[x];e[tot].dis=w;head[x]=tot;}
inline int add(int x,int y){return ((x+y)%3);};
int mson[MAXN],ms,rt,dis[MAXN];
const int inf=(1<<30);
bitset<MAXN>vis;
void Gr(int x,int fa){
	siz[x]=1;mson[x]=0;
	for(int i=head[x];i;i=e[i].nxt){
		int j=e[i].to;
		if(vis[j]||j==fa)continue;
		Gr(j,x);siz[x]+=siz[j];
		if(siz[j]>mson[x])mson[x]=siz[j];
	}
	if(Siz-siz[x]>mson[x])mson[x]=Siz-siz[x];
	if(ms>mson[x])ms=mson[x],rt=x;
}
int t,tmp[4];
void Getdis(int x,int fa,int d){
	dis[++t]=(d%3);tmp[dis[t]]++;
	for(int i=head[x];i;i=e[i].nxt){
		int j=e[i].to;
		if(j==fa||vis[j])continue;
		Getdis(j,x,add(d,e[i].dis));
	}
}
int solve(int x,int d){
	t=0;tmp[1]=tmp[0]=tmp[2]=0;
	Getdis(x,0,d);return (tmp[0]*tmp[0]+tmp[1]*tmp[2]*2);
}
void work(int x,int s){
	vis[x]=1;ans+=solve(x,0);
	for(int i=head[x];i;i=e[i].nxt){
		int j=e[i].to;
		if(vis[j])continue;
		ans-=solve(j,e[i].dis);
		ms=inf;rt=0;Siz=siz[j]<siz[x]?siz[x]:(s-siz[x]);
		Gr(j,0);work(rt,Siz);
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<n;++i){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		link(x,y,z);link(y,x,z);
	}
	rt=0;ms=inf;Siz=n;Gr(1,0);work(rt,n);
	int g=gcd(n*n,ans);
	printf("%d",ans/g);putchar('/');
	printf("%d\n",n*n/g);
	return 0;
} 
posted @ 2020-08-21 12:39  Refined_heart  阅读(99)  评论(0编辑  收藏  举报