POJ1741 tree (点分治模板)
题目大意:
给一棵有 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)。