题意
给定一棵树,每条边有权值, f ( v , u ) f(v,u) f(v,u) 表示 v v v 和 u u u 路径之间仅出现过一次的权值的数量,计算对于所有的点对, f f f 的和。
分析
首先显然要将其转化为边对答案的贡献,考虑对于一个权值的所有边,其将树上的点分成了许多连通块,那么贡献就是相邻连通块的size乘积之和。
做法一:
考虑在DFS的过程中将各个连通块的size处理出来,将每个连通块深度最小的点当作这个连通块的表示点(假设为 u u u),那么我们按照DFS序,在其向下DFS时,DFS到合法的各链上的深度最小的点时,就可以更新 u u u 连通块的size(具体看代码)
那么对于一个权值的关键点,其又形成了一棵树,这个贡献显然就很好算了。
然后第二次DFS把答案算一下就行了。
注意对于每一个权值还需要建一个虚根。
Code:
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=x*10+(c-'0');
c=getchar();
}
return x*f;
}
int n;int cnt;
int a[maxn],b[maxn];
int ver[maxn<<1],nxt[maxn<<1],val[maxn<<1],head[maxn];
vector<int> g[maxn];
int siz[maxn<<1],f[maxn<<1];
int ans;
inline void add(int x,int y,int z)
{
cnt++;
ver[cnt]=y;
nxt[cnt]=head[x];
val[cnt]=z;
head[x]=cnt;
}
inline void dfs1(int x,int fa)
{
siz[x]=1;
for(int i=head[x];i;i=nxt[i])
{
if(ver[i]==fa) continue;
g[val[i]].push_back(ver[i]);
dfs1(ver[i],x);
siz[x]+=siz[ver[i]];
g[val[i]].pop_back();
f[g[val[i]].back()]-=siz[ver[i]];
}
f[x]+=siz[x];
}
inline void dfs2(int x,int fa)
{
for(int i=head[x];i;i=nxt[i])
{
if(ver[i]==fa) continue;
g[val[i]].push_back(ver[i]);
dfs2(ver[i],x);
g[val[i]].pop_back();
ans+=f[g[val[i]].back()]*f[ver[i]];
}
}
int gcd(int a,int b){ return b==0?a:gcd(b,a%b); }
signed main()
{
n=read();
rep(i,1,n-1)
{
int x=read(),y=read(),z=read();
add(x,y,z);add(y,x,z);
}
rep(i,1,n)
{
g[i].push_back(i+n);
f[n+i]=n;
}
dfs1(1,0);
dfs2(1,0);
cout<<ans<<endl;
}
做法二:
事实上,这道题可以用LCT以 O ( n l o g n ) O(nlogn) O(nlogn) 轻松解决。
对于每一个权值,将其所有的边全部cut掉,然后再将此权值的每条边连接的两个连通块size相乘贡献进答案,然后再将这些边link回来。
在有板子的情况下大概用时只需要1min写完。
主函数Code:
int n;
vector<pair<int,int> > edge[N];
int main()
{
n=read();
rep(i,1,n) pushup(i);
rep(i,2,n)
{
int x,y,z;
x=read(),y=read(),z=read();
edge[z].pb({x,y});
link(x,y);
}
ll ans=0;
rep(i,1,n)
{
for(auto [x,y]:edge[i]) cut(x,y);
for(auto [x,y]:edge[i]) ans+=get_size(x)*get_size(y);
for(auto [x,y]:edge[i]) link(x,y);
}
cout<<ans<<endl;
}