聰聰可可(BZOJ 2152)
阿新 • • 發佈:2018-12-16
題意
給定一棵邊帶權樹,問有多少個點對,其簡單路徑上的權值和為3的倍數
分析
這和之前做過的Tree POJ 1741有異曲同工之妙,解決樹上的點對(路徑)關係,我們自然想到了分治演算法
問題就變得簡單起來,我們只需要思考如何統計路徑和為3的倍數的點對,其他的就按照點分治的一般套路走
上一道例題,我們是記錄的dis[i],表示 i 這個點到重心的距離,然後按距離排序,二分,求得方案數
搬到這道題上,顯然行不通了,如果還是按同樣的定義,我們就只能n2統計(n ☞ 點的個數)
怎麼辦呢?我也不知道啊
但題解知道:統計一下每個點到重心的距離模 3 後的值出現的次數,即 sum[i] 就表示到重心路徑模 3 為 i 的點的總數(當然 i 只可能為 0,1 或者 2 )
那麼總共的 ans=sum0∗sum0+2∗sum1∗sum2
還要乘 2 的原因是順序不同的點對是不同的(比如 (2,3),(3,2) 是不同的)
當然由於重複計算,在遞迴到子樹時,還要將部分答案減掉
而總情況顯然是 n2
程式碼
#include<bits/stdc++.h>
#define in read()
#define N 20009
using namespace std;
inline int read(){
char ch;int f=1,res=0;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
while(ch>='0'&&ch<='9'){
res=(res<<3)+(res<<1)+ch-'0';
ch=getchar();
}
return f==1 ?res:-res;
}
int n,maxn,ans=0,sum[5],G;
int nxt[N<<1],to[N<<1],head[N],w[N<<1],cnt=0;
int sze[N],son[N];
bool vis[N];
void add(int x,int y,int z){
nxt[++cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
nxt[++cnt]=head[y];head[y]=cnt;to[cnt]=x;w[cnt]=z;
}
int gcd(int x,int y){
int z=x% y;
while(z){x=y;y=z;z=x%y;}
return y;
}
void getsize(int u,int fu){
sze[u]=1;son[u]=0;
for(int e=head[u];e;e=nxt[e]){
int v=to[e];
if(!vis[v]&&v!=fu){
getsize(v,u);
sze[u]+=sze[v];
if(sze[v]>son[u]) son[u]=sze[v];
}
}
}
void getG(int rt,int u,int fu){//based on now
son[u]=max(son[u],sze[rt]-sze[u]);
if(son[u]<maxn){
maxn=son[u];
G=u;
}
for(int e=head[u];e;e=nxt[e]){
int v=to[e];
if(!vis[v]&&v!=fu) getG(rt,v,u);
}
}
void getdis(int u,int fu,int d){
sum[d%3]++;
for(int e=head[u];e;e=nxt[e]){
int v=to[e];
if(!vis[v]&&v!=fu){
getdis(v,u,d+w[e]);
}
}
}
int calc(int u,int L){
memset(sum,0,sizeof(sum));
getdis(u,0,L);
return sum[0]*sum[0]+sum[1]*sum[2]*2;
}
void solve(int u){
maxn=n;
getsize(u,0);
getG(u,u,0);
vis[G]=1;
ans+=calc(G,0);
for(int e=head[G];e;e=nxt[e]){
int v=to[e];
if(!vis[v]){
ans-=calc(v,w[e]);
solve(v);
}
}
}
int main(){
n=in;
int i,j,k;
for(i=1;i<n;++i){
int x=in,y=in,w=in;
add(x,y,w);
}
solve(1);
n*=n;
int gd=gcd(ans,n);
ans/=gd;n/=gd;
printf("%d/%d",ans,n);
return 0;
}