关于树的重心的一些性质都没有理解的很好 在此总结一下。
树的重心的应用 点分治 点分树 动态维护树的重心等等。
关于点分治的复杂度一论 这个其实是套用了 分治的思想 或者是CDQ 的思想 对区间不断的进行递归分治,这个分治可以很快的分出断点mid 而在树上我们就无法快速得出这个mid了。
分治区间的复杂度证明 每次都将区间二分 这样不断的分支下去 就是logn层 每层我们对每个点进行O(1)的操作的话 那么每层由于加起来点数==n 故总体复杂度是nlogn的。
但是我们只进行分治不进行运算的话 犹如线段树的建树过程 每个点都没有看 只看区间 那么显然的是 这个复杂度是一个常数时间我们发现这个二叉树的节点个数为2n 所以复杂度为2n
这是通过整体来看复杂度。接下来观察点分治的复杂度来源。
首先我们先选取重心 此时保证每个子树都<=当前分治的所有节点的一半然后对子树再进行这个分治的过程 经过观察 发现只递归了logn层 其中每层点的个数加起来约等于总点数 故当把每个点都扫一遍的时候复杂度 为nlogn
如果加入一些其他的操作的话就是nlogn^2的了。那关于在分治之前的 求树的重心的操作呢 具体的我们发现每一层中的点数为n 然后对当前这层总体求 重心总体复杂度为O(n) 故复杂度也为nlogn。
考虑点分树 点分树其实就是把点分治中的那些可以当做重心的点 以某种形式连接起来。这样我们做动态的修改信息的时候从某个节点暴力的向上跳的复杂度 严格logn 而构出这个点分树的复杂度也为nlogn。(当然我还不会写)
考虑如何动态维护树的重心 即删除当前树的某个节点 在某个节点出再加一个儿子或者再加一棵树,这样的话我们需要LCT动态的维护树的重心了,维护这个东西需要先知道几个知识点:
1 重新说明树的重心 从子树大小来看最大的子树<=整棵树的的一半 从距离上来看 树上所有点到重心的距离之和<=所有点到另外一点的树的重心之和。这个很容易思考的脑补一下就好了。
2 此外值得一提的是树的重心至多有两个 这个也很容易证明:我们在比较树的重心的时候一定是某一条边上的两个节点之间的比较如果这两个点都是重心的话此时考虑添加第三个重心添到这两个点的子树之中一定不可能。连接某个节点此时树的重心变为这个节点了 以为以刚加这个点为重心 子树大小是超过整体的一半的,对于刚刚另一个点也是如此。
综上 树的重心最多有两个。同时拥有奇数个节点的树只有一个重心。
3 把两棵树通过某一个点相连得到一颗新的树,新的树的重心必然在连接原来两颗树的重心的路径上。
这个很显然 的就可以证明出来了 如果不会的话认识我的人可以找我 当面证明 口述比较直观。描述不太好。
4 一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。这个超级的显然对吧。
此题让我动态维护树的重心 两颗树连在一起如何快速寻找到重心 显然我们可以不断的在重心之间的路径上苟,然后寻找新的重心 由于是splay 所以比较平衡我们每次暴力找复杂度logn 树和树之间的合并均摊logn
故整体复杂度mlogn。
//#include<bits/stdc++.h> #include<iostream> #include<iomanip> #include<ctime> #include<cstring> #include<string> #include<ctime> #include<cctype> #include<cstdio> #include<utility> #include<queue> #include<stack> #include<deque> #include<map> #include<set> #include<bitset> #include<vector> #include<algorithm> #include<cstdlib> using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } const int MAXN=100010; int n,ans,m,top; char ch; int c[MAXN][2],s[MAXN],f[MAXN],sz[MAXN],si[MAXN],r[MAXN],fa[MAXN]; inline int get(int x){return c[f[x]][1]==x||c[f[x]][0]==x;} inline int getfather(int x){return x==fa[x]?x:fa[x]=getfather(fa[x]);} inline void swap(int &x,int &y){int tmp=x;x=y;y=tmp;} inline void reversal(int x) { swap(c[x][0],c[x][1]); r[x]^=1; } inline void pushup(int x) { sz[x]=sz[c[x][0]]+sz[c[x][1]]+si[x]+1; } inline void pushdown(int x) { if(r[x]) { reversal(c[x][0]); reversal(c[x][1]); r[x]=0; } } inline void rotate(int x) { int old=f[x],oldf=f[old],k=c[old][1]==x; c[old][k]=c[x][k^1];c[x][k^1]=old; if(get(old))c[oldf][c[oldf][1]==old]=x; if(c[old][k])f[c[old][k]]=old; f[x]=oldf;f[old]=x;pushup(old); } inline void splay(int x) { top=0;int y=x; s[++top]=y; while(get(y))s[++top]=y=f[y]; while(top)pushdown(s[top--]); while(get(x)) { int old=f[x],oldf=f[old]; if(get(old))rotate(((c[old][0]==x)^(c[oldf][0]==old))?x:old); rotate(x); } pushup(x); } inline void access(int x) { for(int y=0;x;x=f[y=x]) splay(x),si[x]+=sz[c[x][1]]-sz[y],c[x][1]=y,pushup(x); } inline void make_root(int x) { access(x);splay(x);reversal(x); } inline void extract(int x,int y) { make_root(x); access(y); splay(y); } inline void Link(int x,int y) { extract(x,y); f[x]=y;si[y]+=sz[x]; pushup(y); } inline int merge(int x) { int mid=sz[x]>>1,odd=sz[x]&1; int ls=0,rs=0,maxx=n+1; while(x) { pushdown(x); int l=ls+sz[c[x][0]],r=rs+sz[c[x][1]]; if(l<=mid&&r<=mid) { if(x<maxx)maxx=x; if(odd)break; } if(l>r)rs+=sz[c[x][1]]+si[x]+1,x=c[x][0]; else ls+=sz[c[x][0]]+si[x]+1,x=c[x][1]; } splay(maxx); return maxx; } int main() { //freopen("1.in","r",stdin); n=read();m=read(); for(int i=1;i<=n;++i)fa[i]=i,sz[i]=1,ans^=i; for(int i=1;i<=m;++i) { ch=getc(); int x,y; while(ch!='A'&&ch!='Q'&&ch!='X')ch=getc(); if(ch=='X') { ch=getc();ch=getc(); printf("%d ",ans); } if(ch=='A') { x=read();y=read(); Link(x,y); int fx=getfather(x); int fy=getfather(y); extract(fx,fy);//提取 提炼 int root=merge(fy); ans=ans^fx^fy^root; fa[fx]=fa[fy]=fa[root]=root; } if(ch=='Q')x=read(),printf("%d ",getfather(x)); } return 0; }