【平衡树启发式合并】POJ1741[Tree]题解
来源:互联网 发布:矩阵论导教导学导考 编辑:程序博客网 时间:2024/06/03 17:56
题目概述
给出一棵树,求dis(x,y)<=K的点对(x,y)有多少,dis(x,y)表示x到y的最短距离(x<y)。
解题报告
这道题是明显的点分,就不阐述点分了,主要讲讲另一种解法:平衡树+启发式合并,效率也很不错:O(n*log2^2(n))。
说的这么高大上,实际上就是暴力啦。先找出根节点,然后Dfs递归处理。对于每一个节点,都建立一棵平衡树(作者蒟蒻,这里使用Treap),然后从叶往根上推(从叶往根上推根据Dfs的深度优先就可以轻松实现)。举个例子,如图:
首先先把son1的Treap的所有节点(直接遍历即可)和fa的Treap进行累加,累加后,把fa加入当son1中。处理完毕后,根据启发式合并的思想,先比较fa的Treap的平衡树大小和son2的Treap的平衡树大小,把小的往大的身上累加,然后把小的加入到大的中。以此类推,直到处理完所有son。
但是如何计算两个点之间的距离?其实很简单(当然也可以直接用lazy_tag,留给你们自己思考啦:P):
记录dis[i]表示i节点在这棵树中的深度。由图所示,son1和son2的距离就是dis[son1]+dis[son2]-2*dis[fa],不难发现2*dis[fa]可以独立领出来统一减去,那么问题就简化为dis[son1]+dis[son2]了。
ps:这样的话,就有一个细节了:fa不能在刚开始就加入Treap,否则统计会出错(fa和其他节点的距离是dis[son]-dis[fa],减去2*dis[fa]会出错)。
其实至此,这道题目就讲完了,再来谈谈启发式合并复杂度的问题:由于是小的加入到大的身上,所以原先小的树的节点所在的树的个数必定*2,而树的个数最多是n。所以每个节点顶多被加入log2(n)次,而每次加入的复杂度是log2(n),所以总时间复杂度为O(n*log2^2(n))。
示例程序
#include<cstdio>#include<cstring>#include<cstdlib>#include<algorithm>using namespace std;const int maxn=10005,maxm=20005,maxt=140005;struct Treap{ Treap *son[2]; int x,w,si,p; int cmp(int k) {if (k<x) return 0; else if (k>x) return 1;return -1;} void Pushup() {si=w+son[0]->si+son[1]->si;}}; //Treap的结构体Treap tem[maxt],*null=&tem[0],*len=null,*ro[maxn]; //用null代替NULLint n,K,son[maxm],nxt[maxm],lnk[maxn],w[maxm],E,ans;bool vis[maxn];void Add(int x,int y,int z) {son[++E]=y;w[E]=z;nxt[E]=lnk[x];lnk[x]=E;}Treap *newTreap(int k,int num) {len++;len->w=len->si=num;len->x=k;len->p=rand();len->son[0]=len->son[1]=null;return len;}void rotate(Treap* &id,int f){ Treap *t=id->son[f^1];id->son[f^1]=t->son[f];t->son[f]=id; id->Pushup();t->Pushup();id=t;}void Insert(Treap* &id,int k,int num){ if (id==null) {id=newTreap(k,num);return;} int f=id->cmp(k);id->si+=num; if (f==-1) id->w+=num; else { Insert(id->son[f],k,num); if (id->son[f]->p>id->p) rotate(id,f^1); }}int Asksum(Treap* id,int k) //询问<=k的节点有多少{ if (id==null) return 0; int f=id->cmp(k); if (f==-1) return id->son[0]->si+id->w; else if (f==0) return Asksum(id->son[0],k); else if (f==1) return id->son[0]->si+id->w+Asksum(id->son[1],k);}void Join(Treap* &A,Treap* B) //把B插入A中{ if (B==null) return; Insert(A,B->x,B->w); Join(A,B->son[0]);Join(A,B->son[1]);}int Count(Treap* A,Treap* B,int dis) //统计B所有节点在A中的满足个数{ if (B==null) return 0; return B->w*Asksum(A,dis-B->x)+Count(A,B->son[0],dis)+Count(A,B->son[1],dis); //注:这里一定要乘上B->w,因为B->x不一定只有一个}void Dfs(int x,int dep){ vis[x]=true; for (int j=lnk[x];j;j=nxt[j]) if (!vis[son[j]]) { Dfs(son[j],dep+w[j]); //先处理下面 if (ro[x]->si<ro[son[j]]->si) swap(ro[x],ro[son[j]]); //启发式合并,小的往大的身上累加和合并 ans+=Count(ro[x],ro[son[j]],K+2*dep);Join(ro[x],ro[son[j]]); } ans+=Asksum(ro[x],K+dep); //最后统计x Insert(ro[x],dep,1); //把x插入进去}int main(){ freopen("program.in","r",stdin); freopen("program.out","w",stdout); for (scanf("%d%d",&n,&K);n||K;scanf("%d%d",&n,&K)) { memset(lnk,0,sizeof(lnk));E=0;memset(vis,0,sizeof(vis));ans=0; for (int i=1;i<=n;i++) ro[i]=null;len=null; for (int i=1;i<=n-1;i++) { int x,y,z;scanf("%d%d%d",&x,&y,&z); Add(x,y,z);Add(y,x,z); } Dfs(1,0); printf("%d\n",ans); } return 0;}
- 【平衡树启发式合并】POJ1741[Tree]题解
- poj1741 Tree,平衡树,启发式合并
- [平衡树+启发式合并 || 点分治] POJ1741 Tree
- hdu4812 D Tree,平衡树,启发式合并
- 【楼天城男人八题】【树分治|Treap+启发式合并】POJ1741 Tree
- poj1741:Tree (树上点分治/treap+启发式合并)
- POJ1741 Tree (树上点分治/treap+启发式合并)
- CSU 1811 Tree Intersection 平衡树启发式合并
- 【BZOJ】【P3545】【ONTAK2010】【Peaks】【题解】【离线+并查集+平衡树启发式合并】
- 【并查集+平衡树启发式合并】LibreOJ β Round #2[DP一般看规律]题解
- [APIO2012]派遣 (平衡树启发式合并)
- BZOJ2733 [HNOI2012]永无乡 平衡树启发式合并
- [BZOJ2733]HNOI2012永无乡|平衡树启发式合并
- [BZOJ2733][HNOI2012][启发式合并][平衡树]永无乡
- [平衡树+启发式合并] BZOJ2733: [HNOI2012]永无乡
- hdu 4670 Cube number on a tree,平衡树,启发式合并
- 【BZOJ】【P2212&P3702】【Poi2011】【Tree Rotations】【二叉树】【题解】【启发式合并】
- 【树的点分治】【平衡树】[POJ1741]Tree
- CUDA学习--3
- leetcode题解-336. Palindrome Pairs
- CMakeLists.txt文件详解
- POJ 3233
- 解线性同余方程的应用(一)
- 【平衡树启发式合并】POJ1741[Tree]题解
- pat 1043. Is It a Binary Search Tree (25)
- oracle的分页(rownum)
- A.2多任务网段扫描练习 (增加功能)
- ZCMU—C
- Mongo Group
- 质数求法
- uva 851 Flooded!
- Servlet之间的跳转