1. 程式人生 > >POJ1741 樹中點對統計

POJ1741 樹中點對統計

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;
}