点分治
来源:互联网 发布:网络推广外包合同 编辑:程序博客网 时间:2024/04/30 06:36
点分治
找到一棵树的重心,然后把这棵树拆开,这样就变成了另外几棵树,一直递归下去求解。
每次讨论三种情况:
1. 属于同一子树的顶点对(v,w),会在递归中被解决。
2. 属于不同子树的顶点对(v,w),要去掉和第一种重复的部分。
3. 重心s和其他顶点v组成的顶点对(s,v),可以巧妙地变为第二种。
例题 Poj 1741
给定一棵N(10^5)个顶点的树,边带正权,问你距离不超过k的顶点对数。
解析
将无根树转化成有根树进行观察。满足条件的点对有两种情况:两个点的路径横跨树根,两个点位于同一颗子树中。
如果我们已经知道了此时所有点到根的距离a[i],a[x]+a[y]<=k的(x, y)对数就是结果,这个可以通过排序之后O(n)的复杂度求出。然后根据分治的思想,分别对所有的儿子求一遍即可,但是这会出现重复的——当前情况下两个点位于一颗子树中,那么应该将其减掉。
因为我们每次都找树的重心,所以子树规模会很小。
时间复杂度O(nlog^2n)。
Q:如何找重心?
A:根据重心的定义,我们应该找到使最大子树顶点最少的点。
//找重心,最后重心是那个返回值的.secondtypedef pair<int,int> Point;Point calc_centroid(int U,int Fa,int nn){ Point res=make_pair(2147483647,-1); int sum=1,maxv=0; for(int i=first[U];i;i=next[i]) if(v[i]!=Fa&&(!centroid[v[i]])) { res=min(res,calc_centroid(v[i],U,nn)); maxv=max(maxv,size[v[i]]); sum+=size[v[i]]; } maxv=max(maxv,nn-sum); res=min(res,make_pair(maxv,U)); return res;}
Q:如何去重?
A:我们可以把前面已经访问过的子树情况塞到一起,每次结束一棵子树时只考虑和前面的有多少种可能就好啦。
附上例题代码
#include<bits/stdc++.h>using namespace std;#define MAXN 10001typedef pair<int,int> Point;int n,K,ans;int v[MAXN<<1],w[MAXN<<1],first[MAXN],next[MAXN<<1],en;void AddEdge(const int &U,const int &V,const int &W){ v[++en]=V; w[en]=W; next[en]=first[U]; first[U]=en;}bool centroid[MAXN];//顶点是否已经作为重心删除的标记int size[MAXN];//以该顶点为根的子树的大小//计算子树的大小 int calc_sizes(int U,int Fa){ int res=1; for(int i=first[U];i;i=next[i]) if(v[i]!=Fa&&(!centroid[v[i]])) res+=calc_sizes(v[i],U); return size[U]=res;}//查找重心的递归函数,nn是整个子树的大小//在以U为根的子树中寻找一个顶点,使得删除该顶点后得到的最大子树的顶点数最少//返回值为(最大子树的顶点数,顶点编号)Point calc_centroid(int U,int Fa,int nn){ Point res=make_pair(2147483647,-1); int sum=1,maxv=0; for(int i=first[U];i;i=next[i]) if(v[i]!=Fa&&(!centroid[v[i]])) { res=min(res,calc_centroid(v[i],U,nn)); maxv=max(maxv,size[v[i]]); sum+=size[v[i]]; } maxv=max(maxv,nn-sum); res=min(res,make_pair(maxv,U)); return res;}int td[MAXN],en2,ds[MAXN],en3;//计算子树中所有顶点到重心的距离的递归函数void calc_dis(int U,int Fa,int d){ td[en2++]=d; for(int i=first[U];i;i=next[i]) if(v[i]!=Fa&&(!centroid[v[i]])) calc_dis(v[i],U,d+w[i]);}int calc_pairs(int dis[],int En){ int res=0; sort(dis,dis+En); for(int i=0;i<En;++i) res+=upper_bound(dis+i+1,dis+En,K-dis[i])-(dis+i+1); return res;}void solve(int U){ calc_sizes(U,-1); int s=calc_centroid(U,-1,size[U]).second; centroid[s]=1; //情况1:递归统计按重心s分割后的子树中的对数 for(int i=first[s];i;i=next[i]) if(!centroid[v[i]]) solve(v[i]); //情况2:统计经过重心s的对数 en3=0; ds[en3++]=0; for(int i=first[s];i;i=next[i]) if(!centroid[v[i]]){ en2=0; calc_dis(v[i],s,w[i]); ans-=calc_pairs(td,en2);//先把重复统计的部分(即情况1)减掉 memcpy(ds+en3,td,en2*sizeof(int)); en3+=en2; } ans+=calc_pairs(ds,en3); centroid[s]=0;}void init(){ memset(first,0,sizeof(first)); en=ans=0;}int main(){ while(1){ scanf("%d%d",&n,&K); if(!n&&!K) break; init(); int a,b,c; for(int i=1;i<n;++i){ scanf("%d%d%d",&a,&b,&c); AddEdge(a,b,c); AddEdge(b,a,c); } solve(1); printf("%d\n",ans); } return 0;}
阅读全文
1 0
- 树分治-点分治
- 点分治
- 点分治
- 点分治
- 点分治
- 点分治
- 点分治。。。。。
- 点分治
- 点分治
- 点分治
- 点分治
- 点分治
- 树的分治-点分治
- 树分治(点分治+边分治)
- 【Luogu3806】点分治(点分治)
- 【点分治】hdu4670
- POJ 1741 点分治
- 点分治 poj1741
- 1—n之间所有数的平方和与立方和
- 06 Anykey用户数据的存储和加载SecuritySharedPreference
- Retrofit和OkHttp简单的二次封装
- 最小二乘法求回归直线方程的推导过程
- 使用TabLayout撸出多颜色的Tab
- 点分治
- Linux 中常见目录的作用
- C51矩阵键盘
- java选择排序
- 依葫芦画瓢,使用CoordinatorLayout后不能滑动
- 机器学习笔记(八) 神经网络的表示
- 漂亮的课表控件-TimetableView
- Python学习1
- noip2017 Day1 T2 时间复杂度complexity(栈,模拟)