聪聪可可(点分治)

[国家集训队]聪聪可可(luogu)

Solution

我抄我自己

对于正在处理的以 u 为根的树,将子树内路径分成两种

  • 经过 u 点
  • 不经过 u 点

对于第一类,依次遍历根的每个儿子为根的子树,计算出子树内每个点到原根的路径长度,

每遍历完一个儿子为根的子树,计算它以内的点与在它之前遍历的子树内的点组成的路径长度%3=0的点对个数

(详见代码)

对于第二类,处理去掉 u 后分成的每棵树(以这棵树的重心为根)

Code

#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;
const int N=2e4+10;
int ans[3],cur[3],tot,head[N],nxt[N*2],ver[N*2],edge[N*2];
int fz,fm,n,u,v,w,rt,sum,d[N],dis[N],si[N];
bool vis[N];
void add(int u,int v,int w)
{
    ver[++tot]=v,nxt[tot]=head[u],edge[tot]=w,head[u]=tot;
}
void getrt(int u,int fa)
{
    d[u]=0,si[u]=1;
    for(int i=head[u];i;i=nxt[i])
    {
        int v=ver[i];
        if(v==fa || vis[v]) continue;
        getrt(v,u);
        si[u]+=si[v];
        d[u]=max(d[u],si[v]);
    }
    d[u]=max(d[u],sum-si[u]);
    if(d[u]<=d[rt]) rt=u;
}
void getdis(int u,int fa)
{
    ans[dis[u]%3]++;
    for(int i=head[u];i;i=nxt[i])
    {
        int v=ver[i];
        if(v==fa || vis[v]) continue;
        dis[v]=dis[u]+edge[i];
        getdis(v,u);
    }
}
void calc(int u)
{
    cur[0]=cur[1]=cur[2]=0;
    cur[0]=1;
    for(int i=head[u];i;i=nxt[i])
    {
        int v=ver[i];
        if(vis[v]) continue;
        dis[v]=edge[i];
        ans[0]=ans[1]=ans[2]=0;
        getdis(v,u);
        fz+=cur[0]*ans[0]+cur[1]*ans[2]+cur[2]*ans[1];
        cur[0]+=ans[0],cur[1]+=ans[1],cur[2]+=ans[2];
    }
}
void dfs(int u)
{
    vis[u]=true,calc(u);
    for(int i=head[u];i;i=nxt[i])
    {
        int v=ver[i];
        if(vis[v]) continue;
        d[rt=0]=sum=si[v];
        getrt(v,0),dfs(rt);
    }
}
int gcd(int a,int b)
{
    if(a==0 || b==0) return a+b;
    return gcd(b,a%b);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w),add(v,u,w);
    }
    d[rt=0]=sum=n;
    getrt(1,0);
    dfs(1);
    fz=fz*2+n,fm=n*n;
    int x=gcd(fz,fm);
    printf("%d/%d",fz/x,fm/x);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/hsez-cyx/p/12400358.html