1. 程式人生 > 實用技巧 >【題解】聰聰可可

【題解】聰聰可可

題目戳我

\(\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;
}