POJ1741 tree (點分治模板)
阿新 • • 發佈:2022-04-04
題目大意:
給一棵有 n 個頂點的樹,每條邊都有一個長度(小於 1001 的正整數)。
定義 dist(u,v)=節點 u 和 v 之間的最小距離。
給定一個整數 k,對於每一對 (u,v) 頂點當且僅當 dist(u,v) 不超過 k 時才稱為有效。
編寫一個程式,計算給定樹有多少對有效。
演算法分析:
這道題是一道標準的點分治模板題。題目給定的樹是一顆無根樹,我們首先選取點作為根節點,這裡選取樹的重心root作為劃分點,使得將樹劃分得儘量均衡。然後我們統計每個點到根節點的距離,將這些距離加入到一個距離陣列中,排序,用雙指標掃描,就可以統計以root為根的滿足條件的節點數,這裡還要用到容斥的思想,我們對root的子樹v都要減去重複統計的節點數,從v出發重複以上的過程。
要算兩個節點之間的最小距離不超過k,以root為根,則有兩種情況:
1.兩個點在以root為根的同一顆子樹中;
2.兩個點在不同子樹中;
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=10005; 6 int cnt,n,k,ans,head[maxn]; 7 int root,S,size[maxn],f[maxn],d[maxn],dep[maxn]; 8 bool vis[maxn];9 struct edge 10 { 11 int to,next,w; 12 }edge[maxn*2]; 13 14 void add(int u,int v,int w) 15 { 16 edge[++cnt].to=v; 17 edge[cnt].w=w; 18 edge[cnt].next=head[u]; 19 head[u]=cnt; 20 } 21 22 void getroot(int u,int fa)//獲取重心 23 { 24 size[u]=1; 25 f[u]=0;//刪除u後,最大子樹的大小26 for(int i=head[u];i;i=edge[i].next) 27 { 28 int v=edge[i].to; 29 if(v!=fa&&!vis[v]) 30 { 31 getroot(v,u); 32 size[u]+=size[v]; 33 f[u]=max(f[u],size[v]); 34 } 35 } 36 f[u]=max(f[u],S-size[u]);//S為當前子樹總結點數 37 if(f[u]<f[root]) 38 root=u; 39 } 40 41 void getdep(int u,int fa)//獲取距離 42 { 43 dep[++dep[0]]=d[u];//儲存距離陣列 44 for(int i=head[u];i;i=edge[i].next) 45 { 46 int v=edge[i].to; 47 if(v!=fa&&!vis[v]) 48 { 49 d[v]=d[u]+edge[i].w; 50 getdep(v,u); 51 } 52 } 53 } 54 55 int getsum(int u,int dis) //獲取u的子樹中滿足個數 56 { 57 d[u]=dis; 58 dep[0]=0; 59 getdep(u,0); 60 sort(dep+1,dep+1+dep[0]); 61 int L=1,R=dep[0],sum=0; 62 while(L<R) 63 if(dep[L]+dep[R]<=k) 64 sum+=R-L,L++; 65 else 66 R--; 67 return sum; 68 } 69 70 void solve(int u) //獲取答案 71 { 72 vis[u]=true; 73 ans+=getsum(u,0); 74 for(int i=head[u];i;i=edge[i].next) 75 { 76 int v=edge[i].to; 77 if(!vis[v]) 78 { 79 ans-=getsum(v,edge[i].w);//減去重複 80 root=0; 81 S=size[v]; 82 getroot(v,u); 83 solve(root); 84 } 85 } 86 } 87 88 int main() 89 { 90 f[0]=0x7fffffff;//初始化樹根 91 while(scanf("%d%d",&n,&k),n+k) 92 { 93 memset(vis,0,sizeof(vis)); 94 memset(head,0,sizeof(head)); 95 cnt=0;ans=0; 96 for(int i=1;i<=n-1;i++) 97 { 98 int x,y,z; 99 scanf("%d%d%d",&x,&y,&z); 100 add(x,y,z); 101 add(y,x,z); 102 } 103 root=0; 104 S=n; 105 getroot(1,0); 106 solve(root); 107 printf("%d\n",ans); 108 } 109 return 0; 110 }
每次選取重心作為劃分點,點分治最多遞迴O(logn)層,距離陣列排序O(nlogn),總的時間複雜度O(n)。