(从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,查找即可。
代码:
<2> (VK Cup 2012 R1-D) Distance in Tree
边分治,很容易实现,不说了。
代码:不显示。
(最近实在感觉到弱得令人吃惊……这样下去还混什么集训队啊……于是只好去挑难题了……中间对某些知识点有一些见解……就总结在这里了囧……)
(最近见到了比较多的树分治的题目,因此第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 ( ! l && 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 ;
}
#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 ( ! l && 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
边分治,很容易实现,不说了。
代码:不显示。