1. 程式人生 > >[洛谷P4178] Tree

[洛谷P4178] Tree

這道題是比較裸的點分治。

洛谷傳送門

poj1741 傳送門

點分治是樹分治的一種,據說是最常用的。

除了點分治,還有邊分治、鏈分治等等。

點分治的話,每次找到一個點作為根,把樹拆成幾個部分。

先統計與根有關的答案,再在幾個子樹內繼續拆分下去。

顯然那個點最好選樹的重心。

具體到這道題,每次選完根以後,先算一遍距離。

如果n^2暴力統計距離,太慢了。

我們排個序之後,雙指標掃一遍。

如果現在dis[l]+dis[r]大於k,那麼實際上r就沒有用了,因為l++之後的dis[l]肯定比現在的dis[l]大。

所以某個r沒有用了,我們就可以r--了。

每一次統計的答案不僅僅是跨越根的路徑,也包含了不跨根的路徑,會造成重複統計。

所以我們再在每個子樹中分別統計一遍,減掉就行了。

然後再遞迴進行下一層的分治。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 
 6 typedef long long ll;
 7 
 8 int n,k,ans;
 9 int hd[40005],to[80005],nx[80005],len[80005],ec;
10 
11 void edge(int af,int at,int el)
12 {
13     to[++ec]=at;
14 len[ec]=el; 15 nx[ec]=hd[af]; 16 hd[af]=ec; 17 } 18 19 int rt,tp,tot; 20 ll buf[40005],dis[40005]; 21 int sz[40005],f[40005],mx[40005]; 22 int del[40005]; 23 24 void weigh(int p,int fa) 25 { 26 sz[p]=1;mx[p]=0; 27 for(int i=hd[p];i;i=nx[i]) 28 { 29 int tar=to[i]; 30 if
(tar==fa||del[tar])continue; 31 weigh(tar,p); 32 sz[p]+=sz[tar]; 33 mx[p]=max(mx[p],sz[tar]); 34 } 35 mx[p]=max(mx[p],tot-sz[p]); 36 if(mx[p]<mx[rt])rt=p; 37 } 38 39 void dfs(int p,int fa) 40 { 41 buf[++tp]=dis[p]; 42 for(int i=hd[p];i;i=nx[i]) 43 { 44 int tar=to[i]; 45 if(tar==fa||del[tar])continue; 46 dis[tar]=dis[p]+len[i]; 47 dfs(tar,p); 48 } 49 } 50 51 int count(int p,int d0) 52 { 53 dis[p]=d0;tp=0; 54 dfs(p,0); 55 sort(buf+1,buf+tp+1); 56 int l=1,r=tp,ret=0; 57 while(l<r) 58 { 59 if(buf[l]+buf[r]>k)r--; 60 else ret+=r-(l++); 61 } 62 return ret; 63 } 64 65 void conquer(int p) 66 { 67 ans+=count(p,0); 68 del[p]=1; 69 for(int i=hd[p];i;i=nx[i]) 70 { 71 int tar=to[i]; 72 if(del[tar])continue; 73 ans-=count(tar,len[i]); 74 tot=sz[tar];rt=0; 75 weigh(tar,0); 76 conquer(rt); 77 } 78 } 79 80 int main() 81 { 82 scanf("%d",&n); 83 for(int i=1;i<n;i++) 84 { 85 int ff,tt,vv; 86 scanf("%d%d%d",&ff,&tt,&vv); 87 edge(ff,tt,vv); 88 edge(tt,ff,vv); 89 } 90 scanf("%d",&k); 91 tot=mx[0]=n; 92 weigh(1,0); 93 conquer(rt); 94 printf("%d",ans); 95 return 0; 96 }