POJ1741 樹中點對統計
阿新 • • 發佈:2018-12-10
Description
給定一棵N(1<=N<=100000)個結點的帶權樹,每條邊都有一個權值(為正整數,小於等於1001)。定義dis(u,v)為u,v兩點間的最短路徑長度,路徑的長度定義為路徑上所有邊的權和。再給定一個K(1<=K<=10^9),如果對於不同的兩個結點u,v,如果滿足dist(u,v)<=K,則稱(u,v)為合法點對。求合法點對個數。
Input
輸入檔案的第一行包含兩個整數n和k,接下來n-1行每行都有三個整數u,v,l, 表示結點u和結點v之間有一條長l的邊。
Output
輸出檔案只有一行為合法點對的個數。
Sample Input
5 4
1 2 3
1 3 1
1 4 2
3 5 1
Sample Output
8
Hint
【資料範圍】: 對於50%的資料,n<=1000,k<=1000; 對於100%的資料,n<=100000,k<=10^9;
點分治入門題目。首先明確點分治的思想:樹上的優美的暴力,用來解決樹上路徑統計的相關問題。
每一次尋找樹的重心,將樹分成規模更小的子樹,顯然是log級別的。
1、尋找重心,統計重心到子樹的所有距離,將其合併起來(我們只統計經過root的路徑)。
2、因為1操作中,會有在同一顆子樹中的路徑被合併的情況:
A->B->C,A->B->D,在兩兩合併路徑的時候顯然會出現這種不合法情況,我們需要減去不合法的路徑,因此減去子樹中的經過A的合法路徑條數,但是統計root的時候和統計A子樹的時候少了root->A的這條邊的距離,加上即可。
#include<bits/stdc++.h> using namespace std; const int Maxn=100005; struct Edge{ int cnt,h[Maxn],w[Maxn*2],to[Maxn*2],next[Maxn*2]; inline void add(int x,int y,int z){ next[++cnt]=h[x];to[cnt]=y;w[cnt]=z;h[x]=cnt; } }e; #define to e.to[p] int ans; bool vst[Maxn];//是否計算過 int q[Maxn],tmpsiz[Maxn]; inline void getroot(int x,int fa,int &mn,int &root,int totsiz){ tmpsiz[x]=1;int maxsiz=0; for(int p=e.h[x];p;p=e.next[p])if(!vst[to]&&(to^fa)){ getroot(to,x,mn,root,totsiz); tmpsiz[x]+=tmpsiz[to]; maxsiz=max(maxsiz,tmpsiz[to]); } maxsiz=max(maxsiz,totsiz-tmpsiz[x]); if(maxsiz<mn)mn=maxsiz,root=x; } inline void stat(int x,int fa,int dist){ q[++q[0]]=dist; for(int p=e.h[x];p;p=e.next[p]) if(!vst[to]&&(to^fa))stat(to,x,dist+e.w[p]); } inline int calc(int x,int ori,int lim){ q[0]=0;stat(x,0,ori); sort(q+1,q+q[0]+1); int l=1,r=q[0],ret=0; while(l<=r){ if(q[l]+q[r]<=lim)ret+=r-l,++l; else --r; } return ret; } inline void restat(int x,int fa){ tmpsiz[x]=1; for(int p=e.h[x];p;p=e.next[p])if(!vst[to]&&(to^fa)){ restat(to,x); tmpsiz[x]+=tmpsiz[to]; } } inline void Divide(int x,int lim,int totsiz){ int mn=1<<30,root=x; getroot(x,0,mn,root,totsiz);//找x子樹的重心 ans+=calc(root,0,lim); vst[root]=1; restat(root,0);//重新計運算元樹大小 for(int p=e.h[root];p;p=e.next[p])if(!vst[to]){ ans-=calc(to,e.w[p],lim);//容斥 Divide(to,lim,tmpsiz[to]); } } int main(){ int n,k;scanf("%d%d",&n,&k); for(int i=1;i<n;++i){ int x,y,z;scanf("%d%d%d",&x,&y,&z); e.add(x,y,z),e.add(y,x,z); } Divide(1,k,n); printf("%d\n",ans); return 0; }