P2634 [国家集训队]聪聪可可 (点分治)


题意:给定树,求路径权值和是 3 3 3的倍数的个数。

思路:点分治裸题,用一个 p [ 3 ] p[3] p[3]维护。
每次计算贡献: p [ 0 ] 2 + 2 × p [ 1 ] × p [ 2 ] p[0]^2+2\times p[1]\times p[2] p[0]2+2×p[1]×p[2] 即可。
因为答案要求统计有序对,所以是 2 × p [ 1 ] × p [ 2 ] 2\times p[1]\times p[2] 2×p[1]×p[2]
p [ 0 ] 2 p[0]^2 p[0]2的意思是起点有 p [ 0 ] p[0] p[0]个对应的终点也有 p [ 0 ] p[0] p[0]个,避免了重复计算。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e4+5,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define hh(u,i) for(int i=h[u];i;i=e[i].nt)
int n,cnt,h[N],sum,rt,mx[N],sz[N],vis[N],p[3];ll ans;
struct edge{
	int to,nt,w;
}e[N<<1];
ll gcd(ll a,ll b){
	return !b?a:gcd(b,a%b);
}
inline void add(int u,int v,int w){
	e[++cnt]={v,h[u],w},h[u]=cnt;
	e[++cnt]={u,h[v],w},h[v]=cnt;
} 
void grt(int u,int fa){
	mx[u]=0,sz[u]=1;
	hh(u,i){ int v=e[i].to;
		if(v==fa||vis[v]) continue;
		grt(v,u),mx[u]=max(mx[u],sz[v]),sz[u]+=sz[v];
	}
	mx[u]=max(mx[u],sum-sz[u]);if(mx[u]<mx[rt]) rt=u;
}
void dfs(int u,int fa,int d){
	++p[d];
	hh(u,i){int v=e[i].to;
		if(v==fa||vis[v]) continue;
		dfs(v,u,(d+e[i].w)%3);
	} 
}
ll cal(int u,int d){
	p[0]=p[1]=p[2]=0;dfs(u,0,d);return (1LL*p[0]*p[0]+2LL*p[1]*p[2]);
}
void solve(int u){
	vis[u]=1,ans+=cal(u,0);hh(u,i){ int v=e[i].to;
		if(vis[v]) continue;ans-=cal(v,e[i].w%3),mx[rt=0]=sum=sz[v],grt(v,u),solve(rt);
	}
}
int main(){
	scanf("%d",&n);for(int i=1,u,v,w;i<n;i++) scanf("%d%d%d",&u,&v,&w),add(u,v,w);
	mx[rt]=sum=n,grt(1,0),solve(rt);ll g=gcd(ans,1LL*n*n);printf("%lld/%lld\n",ans/g,1LL*n*n/g); 
	return 0;
}

思路2:非容斥做法。

用桶 n o w [ ] now[] now[]维护,tmp [ ] [] []记录当前子树的路径权值情况。
每次的贡献为: ( t m p [ 0 ] × n o w [ 0 ] + t m p [ 1 ] × n o w [ 2 ] + t m p [ 2 ] × n o w [ 1 ] + t m p [ 0 ] ) × 2 (tmp[0]\times now[0]+tmp[1]\times now[2]+tmp[2]\times now[1]+tmp[0])\times2 (tmp[0]×now[0]+tmp[1]×now[2]+tmp[2]×now[1]+tmp[0])×2
解释以下:分别是当前的路径权值与之前的进行乘法原理,因为是有序对,所以需要乘2,这里的 t m p [ 0 ] tmp[0] tmp[0]单链也要算入贡献。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e4+5,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define hh(u,i) for(int i=h[u];i;i=e[i].nt)
int n,cnt,h[N],sum,rt,mx[N],sz[N],vis[N],tmp[3],now[3];ll ans;
struct edge{
	int to,nt,w;
}e[N<<1];
ll gcd(ll a,ll b){
	return !b?a:gcd(b,a%b);
}
inline void add(int u,int v,int w){
	e[++cnt]={v,h[u],w},h[u]=cnt;
	e[++cnt]={u,h[v],w},h[v]=cnt;
} 
void grt(int u,int fa){
	mx[u]=0,sz[u]=1;
	hh(u,i){ int v=e[i].to;
		if(v==fa||vis[v]) continue;
		grt(v,u),mx[u]=max(mx[u],sz[v]),sz[u]+=sz[v];
	}
	mx[u]=max(mx[u],sum-sz[u]);if(mx[u]<mx[rt]) rt=u;
}
void dfs(int u,int fa,int d){
	++tmp[d];
	hh(u,i){int v=e[i].to;
		if(v==fa||vis[v]) continue;
		dfs(v,u,(d+e[i].w)%3);
	} 
}
void cal(int u){
	now[0]=now[1]=now[2]=0;hh(u,i){ int v=e[i].to;if(vis[v]) continue;
		tmp[0]=tmp[1]=tmp[2]=0;
		dfs(v,u,e[i].w%3);
		ans+=(tmp[0]*now[0]+tmp[1]*now[2]+tmp[2]*now[1]+tmp[0])*2;
		for(int j=0;j<3;j++) now[j]+=tmp[j];
	}
}
void solve(int u){
	vis[u]=1,cal(u);hh(u,i){ int v=e[i].to;
		if(vis[v]) continue;mx[rt=0]=sum=sz[v],grt(v,u),solve(rt);
	}
}
int main(){
	scanf("%d",&n);for(int i=1,u,v,w;i<n;i++) scanf("%d%d%d",&u,&v,&w),add(u,v,w);
	mx[rt]=sum=n,grt(1,0),solve(rt);ans+=n;ll g=gcd(ans,1LL*n*n);printf("%lld/%lld\n",ans/g,1LL*n*n/g); 
	return 0;
}

H i n t Hint Hint
更一般性的做法,求路径是 m m m的倍数的个数。

a n s + = ∑ j = 0 m ( t m p [ j ] × n o w [ ( m − j )   m o d   m ] ) ans+=\sum\limits_{j=0}^m(tmp[j]\times now[(m-j)\bmod m]) ans+=j=0m(tmp[j]×now[(mj)modm])

注意每次 c a l ( ) cal() cal()时要初始化 n o w [ 0 ] = 1 now[0]=1 now[0]=1

最后 a n s × 2 + n ans\times 2+n ans×2+n就是最后的答案

void dfs(int u,int fa,int d){
	++tmp[d];
	hh(u,i){int v=e[i].to;
		if(v==fa||vis[v]) continue;
		dfs(v,u,(d+e[i].w)%m);
	} 
}
void cal(int u){
	for(int i=0;i<m;i++) now[i]=0;now[0]=1;hh(u,i){ int v=e[i].to;if(vis[v]) continue;
		for(int j=0;j<m;j++) tmp[j]=0;
		dfs(v,u,e[i].w%m);
		for(int j=0;j<m;j++) ans+=(tmp[j]*now[(m-j)%m]);
		for(int j=0;j<m;j++) now[j]+=tmp[j];
	}
}