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=0∑m(tmp[j]×now[(m−j)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];
}
}