[BZOJ2152]聪聪可可(点分治)

题目描述

传送门

题解

树上的路径问题可以通过点分治来解决。但是关键问题是“3的倍数”如何满足?
假设两点的距离记为两点到根的深度之和,那么一棵树内的合法点对可以表示为:(所有深度%3=0的点)^2+所有深度%3=1的点*所有深度%3=2的点*2。正确性显然。
那么每一次遍历只需要统计每个点深度%3的值就可以了,每一次的结果都可以 O(1) 计算。
总时间复杂度为 O(nlogn)

代码

#include
#include
#include
using namespace std;
#define N 20005
#define INF 2000000000

int n,x,y,z,sum,root,ans;
int tot,point[N],nxt[N*2],v[N*2],c[N*2];
int size[N],big[N],d[N],t[3];
bool vis[N];

void addedge(int x,int y,int z)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=z;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; c[tot]=z;
}
void getroot(int x,int fa)
{
    size[x]=1; big[x]=0;
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa&&!vis[v[i]])
        {
            getroot(v[i],x);
            size[x]+=size[v[i]];
            big[x]=max(big[x],size[v[i]]);
        }
    big[x]=max(big[x],sum-size[x]);
    if (big[x]x;
}
void getdeep(int x,int fa)
{
    t[d[x]]++;
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa&&!vis[v[i]])
        {
            d[v[i]]=(d[x]+c[i])%3;
            getdeep(v[i],x);
        }
}
int calc(int x,int now)
{
    d[x]=now%3;
    t[0]=t[1]=t[2]=0;
    getdeep(x,0);
    return t[1]*t[2]*2+t[0]*t[0];
}
void dfs(int x)
{
    ans+=calc(x,0);
    vis[x]=true;
    for (int i=point[x];i;i=nxt[i])
        if (!vis[v[i]])
        {
            ans-=calc(v[i],c[i]);
            sum=size[v[i]]; root=0;
            getroot(v[i],0);
            dfs(root);
        }
}
int gcd(int a,int b)
{
    if (!b) return a;
    else return gcd(b,a%b);
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i"%d%d%d",&x,&y,&z);
        z%=3;
        addedge(x,y,z);
    }
    sum=n; root=0; big[0]=INF;
    getroot(1,0);
    dfs(root);
    if (!ans)
    {
        puts("0/0");
        return 0;
    }
    int t=gcd(ans,n*n);
    printf("%d/%d",ans/t,n*n/t);
}

你可能感兴趣的:(题解,点分治)