关于树分治的问题

(从NOI以后一直在各网站上做水题……谁叫我这么弱做不动难题呢……)
(最近实在感觉到弱得令人吃惊……这样下去还混什么集训队啊……于是只好去挑难题了……中间对某些知识点有一些见解……就总结在这里了囧……)

(最近见到了比较多的树分治的题目,因此第0个总结的就是树分治了……)

相关论文

树分治用于解决有关路径的问题。
树分治分为点分治和边分治(其实还有一种叫“链分治”,是树的路径剖分思想的更高级的体现,一般链分治的题目都可以用路径剖分解决)。点分治就是每次找到重心,然后把重心去掉,对分成的每两棵树之间分别统计路径信息(以重心的每个相邻点为根,遍历整棵子树即可得到这个根到每个结点的统计信息),就可以知道包含这个重心的所有路径的信息,然后对于剩下的路径就是在子树里面进行同样的操作了,直到只剩一个点为止(注意这一个点所构成的路径有时也要处理一下)。边分治就是每次找到一条边,使得删掉这条边后分成的两棵子树大小尽可能平均,然后以删掉的边的两端点为根,分别统计根到两棵树中的每个结点的路径信息,最后合并算路径,即可得到包含这条边的所有路径的信息,剩下的路径在两棵树中递归处理。

点分治和边分治是可以通用的,不管树上的信息记录在结点上还是边上。这时一个很囧的问题就出现了:这两种分治到底哪个好?这也是本总结讨论的重点。
有关“哪个好”的问题显然要从三种复杂度上考虑。由于点分治和边分治的空间复杂度均为O(N),因此讨论空间复杂度木有意义。所以需要比较的就是时间复杂度和编程复杂度了。
先说时间复杂度。点分治每次找到重心后,需要将重心删掉然后分成很多棵子树,对每两棵子树之间都要统计一下。如果操作是可反的(比如计数问题:统计某种路径的条数),可以通过先将所有子树合在一起统计,再减去两端点处在相同子树内的路径,但是,如果操作不可反(比如找“最优路径”),就不能这样做了,只能枚举所有的子树对。显然,如果有S棵子树,则有O(S 2)个子树对,最坏情况下(星形的树),S=N-1,此时一一枚举子树对必然会导致总时间复杂度升到O(N 2)以上。当然这是有解决办法的,可以从小到大枚举子树,每处理完一棵子树就将其和前面的合并,此时算法效率取决于合并的方法。如果使用归并法(大小为A的和大小为B的合并次数为A+B),对于星形树的合并总次数仍然为O(N 2),如果能保证大小为A的和大小为B的合并次数为min{A, B},则可以保证合并总次数在O(N)以内,从而可以保证总时间复杂度为O(NlogN)。边分治由于分成的永远是两棵子树,所以在处理子树之间的关系上能够保证O(N),问题是它并不能保证每次分成的两棵子树大小非常平均,在最坏情况下(星形的树),不管删哪条边分成的两棵子树大小都是一个1一个(N-1),这样就会使总的时间复杂度上升为O(N 2)。

从以上的讨论中可以看出,点分治和边分治最怕的都是星形的树,也就是那种某个点的度数特别大的树。相关论文中提到一种办法通过加无用点的方式将其转化为二叉树,从而解决这个问题。这样一来,点分治和边分治的时间复杂度都可以保证了。接下来是编程复杂度的问题:点分治需要找重心(三次DFS或BFS),删掉重心(需要删点),且分成最多三棵树,需要处理三对关系,代码量肯定大一些,而边分治只需要算出子树大小后就可以找到最优边,并且分成的是两棵树,更重要的是,在有根树上删掉一条边后,分成的两棵树中有一棵已是有根树,另一棵可以通过扭根的方式将其转化为有根树,由于二叉树扭根是很方便的,所以就不需要每次重新建树,代码量较小。综合以上因素,边分治更好些。

另外,其实那种加无用点转化为二叉树的方式是有问题的,因为它改变了路径的意义,可能导致一条路径由于LCA是无用点而在统计时出现错误(明明是跨越了重心或最优边的路径被当成木有跨越的),但是……这个问题有一种很简单的解决办法,想知道是什么……AC了ALOEXT再告诉你……

相关题目:
<1> (2013年候选队互测•a142857a) 树
边分治,每次找到所有点到根路径的XOR和,以及特殊点的个数,按照特殊点个数排序后有序插入Trie,查找即可。
代码:
#include  < iostream >
#include 
< stdio.h >
#include 
< stdlib.h >
#include 
< string .h >
#include 
< algorithm >
using   namespace  std;
#define  re(i, n) for (int i=0; i<n; i++)
#define  re1(i, n) for (int i=1; i<=n; i++)
#define  re2(i, l, r) for (int i=l; i<r; i++)
#define  re3(i, l, r) for (int i=l; i<=r; i++)
#define  rre(i, n) for (int i=n-1; i>=0; i--)
#define  rre1(i, n) for (int i=n; i>0; i--)
#define  rre2(i, r, l) for (int i=r-1; i>=l; i--)
#define  rre3(i, r, l) for (int i=r; i>=l; i--)
#define  ll long long
const   int  MAXN  =   200010 , MAXS  =   31 , INF  =   ~ 0U   >>   2 ;
struct  edge {
    
int  a, b, pre, next;
} E[MAXN 
<<   1 ];
struct  sss {
    
int  v1, v2;
    
bool   operator <  (sss s0)  const  { return  v1  <  s0.v1;}
} S1[MAXN], S2[MAXN];
int  T[MAXN  *  MAXS][ 2 ];
int  n, m0, K, sum0, __No, N, A[MAXN], Q[MAXN], ls[MAXN], L[MAXN], R[MAXN], pr[MAXN], SZ[MAXN], V1[MAXN], V2[MAXN], res  =   - 1 ;
bool  B[MAXN], vst[MAXN];
void  init_d()
{
    re(i, n) E[i].pre 
=  E[i].next  =  i;  if  (n  &   1 ) m0  =  n  +   1 else  m0  =  n;
}
void  add_edge( int  a,  int  b)
{
    E[m0].a 
=  a; E[m0].b  =  b; E[m0].pre  =  E[a].pre; E[m0].next  =  a; E[a].pre  =  m0; E[E[m0].pre].next  =  m0 ++ ;
    E[m0].a 
=  b; E[m0].b  =  a; E[m0].pre  =  E[b].pre; E[m0].next  =  b; E[b].pre  =  m0; E[E[m0].pre].next  =  m0 ++ ;
}
void  init()
{
    scanf(
" %d%d " & n,  & K); init_d();
    
int  x, y;
    re(i, n) {scanf(
" %d " & x); B[i]  =  x;}
    re(i, n) scanf(
" %d " & A[i]);
    re2(i, 
1 , n) {scanf( " %d%d " & x,  & y); add_edge( -- x,  -- y);}
}
int  dfs0( int  l,  int  r)
{
    
if  (l  >  r)  return   - 1 else   if  (l  ==  r)  return  ls[l];  else  {
        
int  n0;  if  ( ! &&  r  ==  sum0  -   1 ) n0  =  __No;  else  n0  =  n ++ ;
        
int  mid  =  l  +  r  >>   1 , lch  =  dfs0(l, mid), rch  =  dfs0(mid  +   1 , r);
        L[n0] 
=  lch; R[n0]  =  rch; pr[lch]  =  pr[rch]  =  n0;  return  n0;
    }
}
void  prepare()
{
    
int  x, y; Q[ 0 =   0 ; vst[ 0 =   1 ; pr[ 0 =   - 1 ;
    
for  ( int  front = 0 , rear = 0 ; front <= rear; front ++ ) {
        x 
=  Q[front]; sum0  =   0 ;
        
for  ( int  p = E[x].next; p  !=  x; p = E[p].next) {
            y 
=  E[p].b;
            
if  ( ! vst[y]) {ls[sum0 ++ =  y; vst[y]  =   1 ; Q[ ++ rear]  =  y;}
        }
        
if  (sum0  >   1 ) {__No  =  x; dfs0( 0 , sum0  -   1 );}  else   if  (sum0  ==   1 ) {L[x]  =  ls[ 0 ]; pr[ls[ 0 ]]  =  x; R[x]  =   - 1 ;}  else  L[x]  =  R[x]  =   - 1 ;
    }
}
void  solve( int  No,  int  n0)
{
    
if  (n0  ==   1 ) {
        
if  (B[No]  >=  K  &&  A[No]  >  res) res  =  A[No];
        
return ;
    }
    
int  x, _x, y, minv  =  INF, _n0  =  n0  >>   1 ; Q[ 0 =  No;
    
for  ( int  front = 0 , rear = 0 ; front <= rear; front ++ ) {
        x 
=  Q[front];
        
if  ((y  =  L[x])  >=   0 ) Q[ ++ rear]  =  L[x];
        
if  ((y  =  R[x])  >=   0 ) Q[ ++ rear]  =  R[x];
    }
    rre(i, n0) {
        x 
=  Q[i]; SZ[x]  =   1 ;
        
if  (L[x]  >=   0 ) SZ[x]  +=  SZ[L[x]];
        
if  (R[x]  >=   0 ) SZ[x]  +=  SZ[R[x]];
        
if  (SZ[x]  <=  _n0  &&  _n0  -  SZ[x]  <  minv  ||  SZ[x]  >  _n0  &&  SZ[x]  -  _n0  <  minv) {minv  =  SZ[x]  <=  _n0  ?  _n0  -  SZ[x] : SZ[x]  -  _n0; _x  =  x;}
    }
    x 
=  pr[_x]; pr[_x]  =   - 1 if  (L[x]  ==  _x) L[x]  =   - 1 else  R[x]  =   - 1 ;
    
int  sum0  =   1 ; ls[ 0 =  x;  while  ((y  =  pr[x])  !=   - 1 ) { if  (L[y]  ==  x) L[y]  =   - 1 else  R[y]  =   - 1 if  (L[x]  ==   - 1 ) L[x]  =  y;  else  R[x]  =  y; x  =  ls[sum0 ++ =  y;}
    pr[ls[
0 ]]  =   - 1 ; re2(i,  1 , sum0) pr[ls[i]]  =  ls[i  -   1 ];
    
int  len1  =   0 , len2  =   0 ;
    Q[
0 =  _x; V1[_x]  =  B[_x]; V2[_x]  =  A[_x]; S1[len1].v1  =  V1[_x]; S1[len1 ++ ].v2  =  V2[_x];
    
for  ( int  front = 0 , rear = 0 ; front <= rear; front ++ ) {
        x 
=  Q[front];
        
if  ((y  =  L[x])  >=   0 ) {Q[ ++ rear]  =  y; V1[y]  =  V1[x]  +  B[y]; V2[y]  =  V2[x]  ^  A[y]; S1[len1].v1  =  V1[y]; S1[len1 ++ ].v2  =  V2[y];}
        
if  ((y  =  R[x])  >=   0 ) {Q[ ++ rear]  =  y; V1[y]  =  V1[x]  +  B[y]; V2[y]  =  V2[x]  ^  A[y]; S1[len1].v1  =  V1[y]; S1[len1 ++ ].v2  =  V2[y];}
    }
    Q[
0 =  ls[ 0 ]; V1[ls[ 0 ]]  =  B[ls[ 0 ]]; V2[ls[ 0 ]]  =  A[ls[ 0 ]]; S2[len2].v1  =  V1[ls[ 0 ]]; S2[len2 ++ ].v2  =  V2[ls[ 0 ]];
    
for  ( int  front = 0 , rear = 0 ; front <= rear; front ++ ) {
        x 
=  Q[front];
        
if  ((y  =  L[x])  >=   0 ) {Q[ ++ rear]  =  y; V1[y]  =  V1[x]  +  B[y]; V2[y]  =  V2[x]  ^  A[y]; S2[len2].v1  =  V1[y]; S2[len2 ++ ].v2  =  V2[y];}
        
if  ((y  =  R[x])  >=   0 ) {Q[ ++ rear]  =  y; V1[y]  =  V1[x]  +  B[y]; V2[y]  =  V2[x]  ^  A[y]; S2[len2].v1  =  V1[y]; S2[len2 ++ ].v2  =  V2[y];}
    }
    sort(S1, S1 
+  len1); sort(S2, S2  +  len2);  int  _len2  =  len2  -   1 ;
    N 
=   1 ; T[ 1 ][ 0 =  T[ 1 ][ 1 =   0 int  v0, _v;
    re(i, len1) {
        
while  (_len2  >=   0   &&  S1[i].v1  +  S2[_len2].v1  >=  K) {
            v0 
=  S2[_len2 -- ].v2; x  =   1 ;
            rre(j, MAXS) {
                y 
=  (v0  &  ( 1   <<  j))  >   0 ;
                
if  ( ! T[x][y]) {T[x][y]  =   ++ N; T[N][ 0 =  T[N][ 1 =   0 ;}
                x 
=  T[x][y];
            }
        }
        
if  (T[ 1 ][ 0 ||  T[ 1 ][ 1 ]) {
            v0 
=  S1[i].v2; x  =   1 ; _v  =   0 ;
            rre(j, MAXS) {
                y 
=  (v0  &  ( 1   <<  j))  >   0 ; _v  <<=   1 ;
                
if  (T[x][ ! y]) {x  =  T[x][ ! y]; _v ++ ;}  else  x  =  T[x][y];
            }
            
if  (_v  >  res) res  =  _v;
        }
    }
    x 
=  _x; y  =  ls[ 0 ];
    solve(x, len1); solve(y, len2);
}
void  pri()
{
    printf(
" %d\n " , res);
}
int  main()
{
    init();
    prepare();
    solve(
0 , n);
    pri();
    
return   0 ;
}

<2> (VK Cup 2012 R1-D) Distance in Tree
边分治,很容易实现,不说了。
代码:不显示。

你可能感兴趣的:(关于树分治的问题)