bzoj 2152(点分治)
题意:
给你一棵树,每条边有权值,现在要求你求出有多少对点对满足他们之间的权值能够被\(3\)整除。
分析:
因为我们需要维护的是树中链的信息,因此我们不妨使用点分治进行统计。因为我们只需要判断是否能够被\(3\)整除,因此我们只需要统计出点对之间的权值和\(\%3\)之后的信息,即我们只需要统计出\(\%3=0\),\(\%3=1\),\(\%3=2\)的信息即可,我们计作\(cnt[i]\)。
对于不在一棵子树上的两点,显然权值\(\%3=1\)的点和权值\(\%3=2\)的可以分别被贡献答案,答案为\(2*cnt[1]*cnt[2]\),同理两个权值\(\%3=0\)的点也可以贡献答案,因此对于不在一棵子树上的两点的贡献为\(cnt[1]*cnt[2]*2+cnt[0]*cnt[0]\)。
而在同一棵子树上的两点因为会加多到他们\(lca\)的权值贡献,所以需要剪掉这部分的贡献。
整体的时间复杂度为:\(\mathcal{O}(nlogn)\)。
代码:
#include <bits/stdc++.h>
#define maxn 20005
using namespace std;
struct Node{
int to,next,val;
}q[maxn<<1];
int head[maxn],cnt=0;
int siz[maxn],Size,dep[maxn],root,minr,CNT[10],res=0,n;
bool vis[maxn];
void init(){
memset(head,-1, sizeof(head));
cnt=0;
}
void add_edge(int from,int to,int val){
q[cnt].to=to;
q[cnt].val=val;
q[cnt].next=head[from];
head[from]=cnt++;
}
void getroot(int x,int fa){
siz[x]=1;
int ret=0;
for(int i=head[x];i!=-1;i=q[i].next){
int to=q[i].to;
if(to==fa||vis[to]) continue;
getroot(to,x);
ret=max(ret,siz[to]);
siz[x]+=siz[to];
}
ret=max(ret,Size-siz[x]);
if(ret<minr) minr=ret,root=x;
}
void getdep(int x,int fa){
CNT[dep[x]]++;
for(int i=head[x];i!=-1;i=q[i].next){
int to=q[i].to;
if(to==fa||vis[to]) continue;
dep[to]=(dep[x]+q[i].val)%3;
getdep(to,x);
}
}
int cal(int x,int pre){
dep[x]=pre;
memset(CNT,0,sizeof(CNT));
getdep(x,x);
return CNT[0]*CNT[0]+CNT[1]*CNT[2]*2;
}
int dfs(int x){
res+=cal(x,0);
vis[x]=1;
for(int i=head[x];i!=-1;i=q[i].next){
int to=q[i].to;
if(vis[to]) continue;
res-=cal(to,q[i].val);
minr=n,Size=siz[to];
getroot(to,-1);
dfs(root);
}
}
int main()
{
scanf("%d",&n);
init();
for(int i=1;i<n;i++){
int from,to,val;
scanf("%d%d%d",&from,&to,&val);
add_edge(from,to,val%3);
add_edge(to,from,val%3);
}
Size=minr=n;
getroot(1,-1);
dfs(root);
int tmp=n*n;
int gcd=__gcd(tmp,res);
printf("%d/%d\n",res/gcd,tmp/gcd);
return 0;
}