[2019.1.15]BZOJ2152 聰聰可可
阿新 • • 發佈:2019-03-17
及其 mat 路徑 long spa main 樹形 lin names ,乘2是因為可以調換鏈的兩端。
明顯的點分治。
我們記\(num_{i,j}\)為點\(i\)及其子樹中的節點與\(i\)之間的路徑的和對3取余為\(j\)的種類數,顯然\(0\le j<3\)。
我們再記\(val_{u,v}\)為兩個直接相連的點\(u,v\)之間的邊權,我們把邊權和距離全部對3取余。
我們發現這個東西很好計算。基礎的樹形DP就好了。
那麽如何統計答案呢?
當我們將一個節點\(v\)合並到它的父節點\(u\)上時,會產生2種新的路徑:
1.以\(u\)為一端,\(v\)及其子樹中的節點為另一端的;
2.以\(u\)已經合並的子樹中的節點為一端,\(v?\)及其子樹中的節點為另一端的。
考慮第一種,種類數就是\(2\times num_{v,3-val_{u,v}}\)
對於第二種,我們記\(sum_i\)為已經合並的子樹中,到\(u\)的距離為\(i\)的節點的數量。
那麽這一種的種類數就是\(2\times \sum_{i=0}^2num_{v,i}\times sum_{3-i-val_{u,v}}\)。
最後輸出的時候記得約分。
code:
#include<bits/stdc++.h> #define V(x) (x>2?x-3:(x<0?x+3:x)) using namespace std; struct edge{ int t,v,nxt; }e[40010]; int n,u,v,w,be[20010],cnt,vis[20010]; long long num[20010][3],ans; void add(const int &x,const int &y,const int &val){ e[++cnt].t=y,e[cnt].v=val,e[cnt].nxt=be[x],be[x]=cnt; } void Upd(const int &x,const int &y,const int &ev,long long *sum){ num[x][ev]+=num[y][0],num[x][V(ev+1)]+=num[y][1],num[x][V(ev+2)]+=num[y][2],ans+=num[y][V(3-ev)]*2; for(int i=0;i<3;++i)ans+=num[y][i]*sum[V(V(3-ev)-i)]*2; sum[ev]+=num[y][0],sum[V(ev+1)]+=num[y][1],sum[V(ev+2)]+=num[y][2]; } void dfs(const int &x){ vis[x]=1,num[x][0]=1,ans++; long long sum[3]; sum[0]=sum[1]=sum[2]=0; for(int i=be[x];i;i=e[i].nxt)!vis[e[i].t]?dfs(e[i].t),Upd(x,e[i].t,e[i].v,sum),0:0; } long long gcd(const long long &x,const long long &y){ return y?gcd(y,x%y):x; } void Print(const long long &x,const long long &y){ long long g=gcd(x,y); printf("%lld/%lld",x/g,y/g); } int main(){ scanf("%d",&n); for(int i=1;i<n;++i)scanf("%d%d%d",&u,&v,&w),add(u,v,w%3),add(v,u,w%3); dfs(1); Print(ans,1ll*n*n); return 0; }
[2019.1.15]BZOJ2152 聰聰可可