UOJ Logo zgjkt的博客

博客

【bzoj2152】聪聪可可

2016-09-02 13:07:54 By zgjkt

题目大意

给出一棵有$n$个节点的无根树

若两个点路径上边的边权和是$3$的倍数,求这样的点对个数

数据范围

$n \leqslant 20000$


题解

学习树分治这段时间,安利一个博客 -> 戳我

对于每次分治的子树,两个点路径有两种情况

  1. 经过根节点

可以把当前分治的这棵树上每一个节点到根节点的路径上边的边权和都记录下来

用$t[i]$表示有多少个节点到根节点的路径上边的边权和,在模$3$意义下为i

然后每次加上$(t[1] \times t[2]+t[2] \times t[1]+t[0] \times t[0])$

  1. 不经过根节点

由于这些点对在情况$1$中被计算过一次

即同在一棵子树中的结点$A,B$,计算中路径多了$LCA(A,B)$到根节点的一段多余的路径

所以需要去掉这些贡献

然后路径不经过根节点的这些点对就可以分治到更小的子树去验证


程序

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define For(i,l,r) for(int i=l;i<=r;i++)
#define goedge(i,x) for(int i=last[x];i;i=e[i].next)
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while (ch<'0'&&ch>'9') {if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') {x=x*10+ch-48;ch=getchar();}
    return x*f;
}

int n,et,ans,root,sum;
struct edge{int to,next,c;}e[50050];
int last[20050],size[20050],f[20050],d[20050],t[5];
int vis[20050];

inline int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
inline void addedge(int u,int v,int c){
    e[++et]=(edge){v,last[u],c};last[u]=et;
    e[++et]=(edge){u,last[v],c};last[v]=et;
}

inline void getroot(int x,int fa){
    size[x]=1,f[x]=0;
    goedge(i,x)
        if (!vis[e[i].to]&&e[i].to!=fa){
            getroot(e[i].to,x);
            size[x]+=size[e[i].to];
            f[x]=max(f[x],size[e[i].to]);
        }
    f[x]=max(f[x],sum-size[x]);
    if (f[x]<f[root]) root=x;
}

void getdeep(int x,int fa){
    t[d[x]]++;
    goedge(i,x)
        if (!vis[e[i].to]&&e[i].to!=fa){
            d[e[i].to]=(d[x]+e[i].c)%3;
            getdeep(e[i].to,x);
        }
}

inline int query(int x,int c){
    t[0]=t[1]=t[2]=0;
    d[x]=c;
    getdeep(x,0);
    return t[1]*t[2]*2+t[0]*t[0];
}

void dfs(int x){
    ans+=query(x,0);
    vis[x]=1;
    goedge(i,x)
        if (!vis[e[i].to]){
            ans-=query(e[i].to,e[i].c);
            root=0;
            sum=size[e[i].to];
            getroot(e[i].to,x);
            dfs(root);
        }
}

int main(){
    n=read();
    For(i,1,n-1){
        int u=read(),v=read(),c=read()%3;
        addedge(u,v,c);
    }
    f[0]=sum=n;
    getroot(1,0);
    dfs(root);
    int g=gcd(ans,n*n);
    printf("%d/%d",ans/g,(n*n)/g);
    return 0;
}

评论

WrongAnswer
我第一次写树分治也是做这题。 这题还有另一种做法: 随便设定一个点作为根,转成有根树。 枚举一个点 $a$ 作为LCA,统计以 $a$ 为LCA且距离为3的倍数的点对个数。 用树形DP求出每个点 $i$ 为根的子树中,到 $i$ 距离除以3余数为0,1,2的点分别有多少个。 最后枚举一遍即可,复杂度 $O(n)$。 注意,统计 $a$ 为LCA时有多少个点对 $(x,y)$ 距离为3的倍数时,需要减掉 $x,y$ 在同一以 $a$ 的某个子节点为根的子树内的情况。

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。