标题：树链剖分
基本定义
树链剖分:
目标: 一颗树,,将其划分为若干条链,,,每个点属于且仅属于一条链,,,
划分的方法很多,,,不过按某种方法划分却有一条很好的性质,,,任意一点到根结点的路径上的链的个数不超过logn条,,,
于是如果能把问题转化为维护点到根结点路径的问题,,再用线性结构维护每条链,,那么复杂度就是 维护链的复杂度*logn,,很优秀了,,,
方法
常见的路径剖分的方法是轻重边剖分，即把树中的边分为轻重两部分，方法：设SZ[i]为以i为根的子树的大小（结点总数），则若点x不是叶结点，则其子结点中SZ值最大的（注意，有多个SZ值最大的子结点应任选一个，只能选一个，防止出现重链相交，引发歧义）点y，边(x, y)称为重边，其余的边都是轻边。首尾相连的重边称为重链（注意一定是自上而下的），则一个很显然的性质是：从根结点到任意点i路径上的轻边与重链的总数都不会超过O(log2N)。然后，对每条重链上的边建立线段树，每当遇到改值操作，若是轻边就直接改，若是重边就在线段树里改；遇到找x、y路径上边权最大值的操作，只要找到LCA(x, y)，然后从x、y开始沿着树边上溯到LCA(x, y)处，对于中间的每条轻边和重链（线段树内）导出最大值即可。
常见作用
求LCA：可以对整棵树作深度优先遍历，记下每个遇到的点（包括向上的和向下的）的编号，形成一个长度为2(N-1)的序列A，然后，找到点x、y在A中的第一次出现的位置（设为FF[x]和FF[y]），则A[FF[x]..FF[y]]中的深度最小的点的编号就是LCA(x, y)，显然这需要RMQ。
具体步骤：
（1）输入部分：建立无根树；
（2）预处理部分：分为6步：
<1>利用BFS将无根树转化为有根树，同时求出有根树中点i（根结点除外）到其父结点的边的编号（设为FA[i]）以及点i的深度（设为DEP[i]）；
<2>自底向上计算每个点的SZ值，同时划分轻重边（对于有根树中的每条边设立Z域，Z=1为重边，Z=0为轻边）；
<3>求出重链，建立线段树：
求重链的方法：由于重链只会在叶结点处结束，因此从每个叶结点开始上溯，直到上溯到根或者遇到轻边为止。为了方便，需要对每个结点i记录以下4个值：UP[i]表示点i所在重链最顶上的那个结点的编号；ord[i]表示点i是其所在重链的上起第几个点（从0开始）；tot[i]表示点i所在重链上有几个结点；root[i]表示点i所在重链建成的线段树的编号（这样经常写的opr(0, n-1, root)就可以表示成opr(0, tot[i]-1, root[i])）。求出重链以后，对沿途经历的所有边的权值倒序写入数组W0，再对数组W0建线段树即可。考虑到不同线段树的大小可能不同，这里采用压缩处理，这样就需要记录每个点的lch、rch编号（见代码）；
<4>对树进行DFS遍历，求出序列A；
<5>求出序列A的倍增最小值，存放在A0[][]里（注意：A和A0中记载的都是编号，而判定大小的关键字是深度）；
<6>求出LOG[i]=log2i（下取整）；
（3）执行部分：对于询问操作，求LCA，不断上溯，对于重链在线段树里找即可，注意线段树的左右端点，尤其是当UP在LCA之上时，只能上溯到LCA处。
划分方法:
先预处理出以每个节点为根的子树所包含的节点的总数,即size,,
然后 每个节点和其size最大的出边(不含父亲方向) 连边(红色边),,,
这样由红色边连在一起的就是一条链,,,,然后剩余的单个的点也看作一条链,,,,
图中共有5条链,,,
1-6-8-10,,,,2-3-4,,,5,,,,7,,,,9
这样我们便达到了目的,,每个点仅属于一条链,,,
编程办法,,,怎么简单怎么来
模拟递归栈,,,
计算机的速度越来越高,,,使题目的数据越来越大,,这样用递归的办法就越来越容易暴栈,,
一个10万的点的链进行递归深搜,,,就几乎一定会暴栈,,,
模拟栈的方法:
参数,,,,如果只有一个参数,,用一个数组既可,,,有多个参数就用结构体存储每个参数,,
自底向上的更新,,,如果没有这种更新,,,直接写既可,,如果有的话,,,每个节点被取出2次,,,第一次不弹栈,,第二次弹掉,,,第一次将子节点入栈,,并处理自顶向下的更新,,,第二次处理自底向上的更新,,,,
LCA代码
编程注意事项：建代码中标Attention的地方。
代码（我这个代码常数巨大，时间达3.61s，不知是肿么搞得，神犇来看一下啊囧）： 
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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--)
const int MAXN = 10001, MAXS = 20, INF = ~0U >> 2;
struct edge {
int a, b, w, pre, next;
bool Z;
} E0[MAXN << 2], E[MAXN + MAXN + 1];
struct node {
int maxv, lch, rch;
} T[MAXN << 2];
int n, m0, m, N, _a[MAXN], _b[MAXN], FA[MAXN], Q[MAXN], SZ[MAXN], DEP[MAXN], W0[MAXN], UP[MAXN], ord[MAXN], root[MAXN], tot[MAXN];
int n1, stk[MAXN], st[MAXN], A[MAXN << 2], A0[MAXN << 2][MAXS], FF[MAXN], LOG[MAXN << 2], l0, r0, x0, res;
bool vst[MAXN];
void init_d()
{
re(i, n) E0[i].pre = E0[i].next = E[i].pre = E[i].next = i;
m0 = m = n;
}
void add_edge0(int a, int b, int w)
{
E0[m0].a = a; E0[m0].b = b; E0[m0].w = w; E0[m0].pre = E0[a].pre; E0[m0].next = a; E0[a].pre = m0; E0[E0[m0].pre].next = m0++;
E0[m0].a = b; E0[m0].b = a; E0[m0].w = w; E0[m0].pre = E0[b].pre; E0[m0].next = b; E0[b].pre = m0; E0[E0[m0].pre].next = m0++;
}
void add_edge(int a, int b, int w)
{
E[m].a = a; E[m].b = b; E[m].w = w; E[m].Z = 0; E[m].pre = E[a].pre; E[m].next = a; E[a].pre = m; E[E[m].pre].next = m++;
}
int mkt(int l, int r)
{
int No = ++N;
if (l == r) {
T[No].maxv = W0[l]; T[No].lch = T[No].rch = 0;
} else {
int mid = l + r >> 1, l_r = mkt(l, mid), r_r = mkt(mid + 1, r); T[No].lch = l_r; T[No].rch = r_r;
if (T[l_r].maxv >= T[r_r].maxv) T[No].maxv = T[l_r].maxv; else T[No].maxv = T[r_r].maxv;
}
return No;
}
void prepare()
{
re(i, n) vst[i] = 0; Q[0] = 0; vst[0] = 1; DEP[0] = 0; FA[0] = -1;
int i0, j0;
for (int front=0, rear=0; front<=rear; front++) {
i0 = Q[front];
for (int p=E0[i0].next; p != i0; p=E0[p].next) {
j0 = E0[p].b;
if (!vst[j0]) {add_edge(i0, j0, E0[p].w); vst[j0] = 1; Q[++rear] = j0; FA[j0] = m - 1; DEP[j0] = DEP[i0] + 1;}
}
}
int maxSZ, x, n0, root0;
rre(i, n) {
i0 = Q[i]; SZ[i0] = 1; maxSZ = 0;
for (int p=E[i0].next; p != i0; p=E[p].next) {SZ[i0] += SZ[j0 = E[p].b]; if (SZ[j0] > maxSZ) {maxSZ = SZ[j0]; x = p;}}
if (SZ[i0] > 1) E[x].Z = 1; //Attention
}
UP[0] = 0; ord[0] = 0; N = 0;
re2(i, 1, n) {
i0 = Q[i]; x = FA[i0]; if (E[x].Z) {UP[i0] = UP[E[x].a]; ord[i0] = ord[E[x].a] + 1;} else {UP[i0] = i0; ord[i0] = 0;}
if (SZ[i0] == 1 && ord[i0]) {
j0 = UP[i0]; n0 = ord[i0];
for (int j=i0; j!=j0; j=E[FA[j]].a) {tot[j] = ord[i0]; W0[--n0] = E[FA[j]].w;} tot[j0] = ord[i0]; //Attention
root0 = mkt(0, ord[i0] - 1); //Attention
for (int j=i0; j!=j0; j=E[FA[j]].a) root[j] = root0; root[j0] = root0; //Attention
}
}
re(i, n) {st[i] = E[i].next; FF[i] = -1;} stk[0] = 0; int tp = 0; n1 = 1; A[0] = FF[0] = 0;
while (tp >= 0) {
i0 = stk[tp]; x = st[i0];
if (x != i0) {
j0 = E[x].b; if (FF[j0] == -1) FF[j0] = n1; A[n1++] = j0; st[i0] = E[x].next; stk[++tp] = j0;
} else {
if (tp) A[n1++] = stk[tp - 1];
tp--;
}
}
rre(i, n1) {
A0[i][0] = A[i]; x = 1;
re2(j, 1, MAXS) if (i + (x << 1) <= n1) {
if (DEP[A0[i][j - 1]] <= DEP[A0[i + x][j - 1]]) A0[i][j] = A0[i][j - 1]; else A0[i][j] = A0[i + x][j - 1]; //Attention
x <<= 1;
} else break;
}
int _x; x = 1;
re(i, MAXS) {
_x = x << 1;
if (_x < n1) re2(j, x, _x) LOG[j] = i; else {re2(j, x, n1) LOG[j] = i; break;}
x = _x;
}
}
void opr0(int l, int r, int No)
{
if (l == l0 && r == l0) T[No].maxv = x0; else {
int mid = l + r >> 1, lch = T[No].lch, rch = T[No].rch;
if (mid >= l0) opr0(l, mid, lch);
if (mid < l0) opr0(mid + 1, r, rch);
if (T[lch].maxv >= T[rch].maxv) T[No].maxv = T[lch].maxv; else T[No].maxv = T[rch].maxv;
}
}
void opr1(int l, int r, int No)
{
if (l >= l0 && r <= r0) {
if (T[No].maxv > res) res = T[No].maxv;
} else {
int mid = l + r >> 1;
if (mid >= l0) opr1(l, mid, T[No].lch);
if (mid < r0) opr1(mid + 1, r, T[No].rch);
}
}
int main()
{
int tests, a0, b0, w0, LCA, LOG0, FF0, FF1, p0, tmp;
char ss[20], ch;
scanf("%d", &tests);
re(testno, tests) {
scanf("%d", &n); init_d();
re(i, n-1) {scanf("%d%d%d", &a0, &b0, &w0); add_edge0(--a0, --b0, w0); _a[i] = a0; _b[i] = b0;}
prepare(); ch = getchar();
while (1) {
scanf("%s", ss);
if (!strcmp(ss, "QUERY")) {
scanf("%d%d%*c", &a0, &b0); --a0; --b0;
if (a0 == b0) res = 0; else res = -INF;
FF0 = FF[a0]; FF1 = FF[b0];
if (FF0 > FF1) {tmp = FF0; FF0 = FF1; FF1 = tmp;}
LOG0 = LOG[FF1 - FF0 + 1];
if (DEP[A0[FF0][LOG0]] <= DEP[A0[FF1 - (1 << LOG0) + 1][LOG0]]) LCA = A0[FF0][LOG0]; else LCA = A0[FF1 - (1 << LOG0) + 1][LOG0];
while (a0 != LCA) {
p0 = FA[a0];
if (E[p0].Z) {
r0 = ord[a0] - 1; if (DEP[UP[a0]] >= DEP[LCA]) l0 = 0; else l0 = ord[LCA]; //Attention
if (l0 <= r0) opr1(0, tot[a0] - 1, root[a0]); //Attention
if (l0) a0 = LCA; else a0 = UP[a0];
} else {
if (E[p0].w > res) res = E[p0].w;
a0 = E[p0].a;
}
}
while (b0 != LCA) {
p0 = FA[b0];
if (E[p0].Z) {
r0 = ord[b0] - 1; if (DEP[UP[b0]] >= DEP[LCA]) l0 = 0; else l0 = ord[LCA]; //Attention
if (l0 <= r0) opr1(0, tot[b0] - 1, root[b0]); //Attention
if (l0) b0 = LCA; else b0 = UP[b0];
} else {
if (E[p0].w > res) res = E[p0].w;
b0 = E[p0].a;
}
}
printf("%d\n", res);
} else if (!strcmp(ss, "CHANGE")) {
scanf("%d%d", &a0, &w0); b0 = _b[--a0]; a0 = _a[a0];
if (FA[b0] == -1 || E[FA[b0]].a != a0) {tmp = a0; a0 = b0; b0 = tmp;}
p0 = FA[b0];
if (E[p0].Z) {
l0 = ord[a0]; x0 = w0; opr0(0, tot[a0] - 1, root[a0]);
} else E[p0].w = w0;
} else break;
}
}
return 0;
}
树的路径剖分是解决树上路径的操作查询问题的有力工具，它还有一些更为强大的应用，以后再来搞……
CPP标程
<PRE class=cpp name="code">/*</PRE>
<PRE class=cpp name="code">节点的标号从1开始
*/
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 10000; //N为节点的个数
struct e{
int v;
e* nxt;
}es[N<<1], *fir[N];
struct node{
int ls, rs; //左右儿子的下标，为-1表示空
int l, r; //区间的左右标号
//数据域,根据不同的需要添加，数据的down和update和线段树的无异
int mid() { return (l + r) >> 1; }
}nodes[N<<1];
int n, en;
int que[N], par[N], dep[N], root[N], seg[N], st[N], ed[N], top[N], sons[N], id[N];
//que用于BFS，par记录父节点，dep记录节点的深度。 root[i]为链i的根节点，seg用于在链上建线段树，
//st[i],ed[i]分别为链i的左右端点，top[i]为链i的顶部的节点，sons[i]为节点i的儿子节点
//id[i]是节点i所属的链的标号，
int ln, cnt, tr; //ln是链的个数，cnt为节点的个数,tr是树的根节点
inline void add_e(int u, int v){
es[en].v = v;
es[en].nxt = fir[u];
fir[u] = &es[en++];
}
inline void newNode(int& id, int l, int r){
nodes[cnt].ls = nodes[cnt].rs = -1;
nodes[cnt].l = l;
nodes[cnt].r = r;
id = cnt++;
}
void build(int& id, int l, int r){ //在剖分出来的链上构建线段树
newNode(id, l, r);
if(l >= r){
//seg[l]为落在这个线段树节点上的原树中的节点
return ;
}
int mid = (l+r)>>1;
build(nodes[id].ls, l, mid);
build(nodes[id].rs, mid+1, r);
}
void initTree(){ //初始化剖分树
//确定父亲
int l, r, u, v, i;
e* cur;
l = r = 0;
que[r++] = tr;
par[tr] = -1;
dep[tr] = 0;
while(l != r){
u = que[l++];
int g = 1;
for(cur = fir[u]; cur; cur = cur->nxt){
if((v = cur->v) != par[u]){
que[r++] = v;
par[v] = u;
dep[v] = dep[u]+1;
}
}
}
//计算子树大小
for(i = 1; i <= n; i++){
sons[i] = 1;
id[i] = -1;
}
for(i = r-1; i >= 0; i--){
u = que[i];
if(par[u] >= 0){
sons[par[u]] += sons[u];
}
}
//剖分链
l = r = 0;
que[r++] = tr;
ln = cnt = 0;
while(l != r){
u = que[l++];
st[ln] = dep[u]; //用节点的深度作为线段树中区间的左右标号
top[ln] = u;
while(u >= 0){
id[u] = ln;
ed[ln] = dep[u];
seg[dep[u]] = u;
int best;
for(cur = fir[u], best=-1; cur; cur = cur->nxt){
if(id[v = cur->v] == -1){
if(best == -1 || (best >= 0 && sons[v] > sons[best])){
best = v;
}
}
}
if(best >= 0){
for(cur = fir[u]; cur; cur = cur->nxt){
if(id[v = cur->v] == -1 && best != v){
que[r++] = v;
}
}
}
u = best;
}
root[ln] = -1;
build(root[ln], st[ln], ed[ln]);
ln++;
}
}
void lqry(int& id, int ql, int qr){
if(id == -1) return ;
if(ql <= nodes[id].l && nodes[id].r <= qr){
return ;
}
if(nodes[id].l == nodes[id].r){
return ;
}
int mid = (nodes[id].l+nodes[id].r)>>1;
if(ql <= mid){
lqry(nodes[id].ls, ql, qr);
}
if(qr > mid){
lqry(nodes[id].rs, ql, qr);
}
}
void qry(int u, int v){ //查询u和v之间的最大值
while(id[u] != id[v]){
if(id[u] > id[v]){
swap(u, v);
}
int b = id[v];
lqry(root[b], st[b], dep[v]);
v = par[top[b]];
}
if(dep[u] > dep[v]){
swap(u, v);
}
lqry(root[id[u]], dep[u], dep[v]);
}
int main(){
return 0;
}
</PRE>
&nbsp;