bzoj 2152 聰聰可可
點分治
題意
給定一棵帶權樹,需要統計路徑長度爲3的倍數的路徑條數。
思路
重學點分治。
主要操作就是找重心,那個getrt函數。mx[x]表示x最大的兒子的size,注意這個最大的兒子包括他父親。所以最後那句mx[x]=max(mx[x], n-sz[x]);
就是統計他父親那邊的size。
getdis和calc函數都是暴力計算,getdis(x,f)是統計x的子樹中的dis情況。
那個work裏面有個地方要注意,最開始ans+=calc(x, 0);
,是計算x爲根的子樹中的對數。但是這樣計算會有重複,所以後面減去ans-=calc(e[i].to, e[i].w);
,減去將e[i].to當成深度w的個數。
在計數的時候,將邊權模3,統計在模3意義下的深度,設tim[x]表示深度爲x的點的個數,那麼答案爲tim[1] * tim[2] * 2 + tim[0] * tim[0]。
代碼
//#include<bits\stdc++.h>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int MAXN=20005;
int gcd(int a, int b)
{
return b==0 ? a : gcd(b, a%b);
}
struct Edge
{
int to, ne;
int w;
}e[MAXN*2+7];
int edgenum, head[MAXN];
void addedge(int from, int to, int w)
{
e[edgenum].to=to, e[edgenum].w=w, e[edgenum].ne=head[from], head[from]=edgenum++;
e[edgenum].to=from, e[edgenum].w=w, e[edgenum].ne=head[to], head[to]=edgenum++;
}
int sz[MAXN], mx[MAXN], root, vis[MAXN];
void getrt(int x, int f, int n)//找x的子樹內的重心root
{
sz[x]=1;mx[x]=0;
for(int i=head[x];~i;i=e[i].ne)
{
int to=e[i].to;
if(to==f||vis[to]) continue;
getrt(to, x, n);
sz[x]+=sz[to];
mx[x]=max(mx[x], sz[to]);
}
mx[x]=max(mx[x], n-sz[x]);
if(mx[x]<mx[root]) root=x;
}
int tim[3];int dis[MAXN];
void getdis(int x, int f)
{
tim[dis[x]]++;
for(int i=head[x];~i;i=e[i].ne)
{
int to=e[i].to;
if(to==f||vis[to]) continue;
dis[to]=(dis[x]+e[i].w)%3;
getdis(to, x);
}
}
int calc(int x, int d)
{
tim[0]=tim[1]=tim[2]=0;
dis[x]=d;
getdis(x, -1);
return tim[0]*tim[0]+tim[1]*tim[2]*2;
}
int ans;
void work(int x)
{
vis[x]=1;
ans+=calc(x, 0);
for(int i=head[x];~i;i=e[i].ne)
{
if(vis[e[i].to]) continue;
ans-=calc(e[i].to, e[i].w);
root=0;
getrt(e[i].to, -1, sz[e[i].to]);
work(root);
}
}
int main()
{
int n;
while(scanf("%d", &n)==1)
{
memset(head, -1, sizeof(head));
for(int i=2;i<=n;i++)
{
int a, b, c;scanf("%d%d%d", &a, &b, &c);
addedge(a, b, c%3);
}
tim[0]=tim[1]=tim[2]=0;
mx[0]=0x3f3f3f3f;ans=0;root=0;
getrt(1, -1, n);
work(root);
int d=gcd(ans, n*n);
printf("%d/%d\n", ans/d, n*n/d);
}
return 0;
}