树链剖分就是把树上的链分成重链和轻链,然后在重链和轻链上进行求和、修改,求最大值,最小值等等。。。。
树链剖分只是把找链这个环节优化到了log(n),具体的总的复杂度是多少,还要看你储存链的数据结构是什么。如果用的是线段树,那么复杂度大概是log(n) * log(n);如果用的是树套树,那么复杂度大概是log(n)*log(n)*log(n);如果是树链剖分加上树套树写区间第k大,那么就是(log(n))^4了。
树链剖分要用到以下一些数组来构造轻链重链:
记siz[v]表示以v为根的子树的节点数,dep[v]表示v的深度,top[v]表示v所在链的顶端节点,fa[v]表示v的父亲,son[v]表示与v在同一重链上的儿子节点,w[v]表示v与父亲节点的连边在线段树中的位置。
以上这些数组可以用两个dfs全部求出来
还要定义以下这些名称:
重儿子:siz[u]为v的字节点中siz最大的,那么u就是v的重儿子。
轻儿子:除此之外的都是轻儿子。
重边:点v与重儿子的连边。
轻边:点v与轻儿子的连边。
重链:由重边组成的路径。
轻链:因为轻边不可能连续,所以轻链就是轻边。
树链剖分有两个性质:
性质1.如果(v,u)为轻边,那么siz[u] * 2 < siz[v] ps:目前没发现这个性质对做题有什么作用。。,可能只是在证明log(n)的算法中有用
性质2.对于树上的任意两点间的路径,所经过的重链和轻链的个数都不超过log(n)。 这个性质保证了找路径的复杂度是log(n)
spoj:375
题意:修改某条边的权值,求x到y中边的最大值
代码
/*
树链剖分,基于边
*/
#include
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 20005;
int n;
struct ppp{
int u,v,nex,c;
}e[maxn * 4];
int tole,head[maxn];
void make_edge(int u,int v,int c){
e[tole].u = u;e[tole].c = c;e[tole].v = v;e[tole].nex = head[u];head[u] = tole++;
}
int siz[maxn],fa[maxn],dep[maxn],son[maxn],id[maxn],top[maxn];
int cnt_node;//这个是边在线段树中的编号
void dfs1(int u,int pre,int depth){
dep[u] = depth;siz[u] = 1;son[u] = 0;fa[u] = pre;
for(int i = head[u];~i;i = e[i].nex){
int v = e[i].v;
if(v == fa[u])continue;
dfs1(v,u,depth + 1);
siz[u] += siz[v];
if(siz[son[u]] < siz[v])//找到重儿子
son[u] = v;
}
}
void dfs2(int u,int pre){
top[u] = pre;
id[u] = ++cnt_node;//给当前边在线段树上编号
if(son[u])dfs2(son[u],pre);//不是叶子节点的时候往重链走
for(int i = head[u];~i;i = e[i].nex){
int v = e[i].v;
if(v == fa[u] || v == son[u])continue;
dfs2(v,v);
}
}//至此所有重链轻链以编号并求出
void init(){
scanf("%d",&n);
tole = cnt_node = 0;
for(int i = 0;i <= n;i++){
head[i] = -1;siz[i] = dep[i] = son[i] = id[i] = top[i] = 0;
}
for(int i = 1,a,b,c;i < n;i++){
scanf("%d%d%d",&a,&b,&c);
make_edge(a,b,c);
make_edge(b,a,c);
}
}
char ques[50];
int x,y;
struct pp{
int l,r,mid,c;
pp(){}
void make(int _l,int _r){
l = _l,r = _r,mid = (l + r) >> 1;
}
}node[maxn * 4];
int ori[maxn];
void update(int o){
node[o].c = max(node[o << 1].c,node[o << 1 | 1].c);
}
void build(int l,int r,int o){
node[o].make(l,r);
if(l == r){
node[o].c = ori[l];
return;
}
int mid = (l + r) >> 1;
build(l,mid,o << 1);
build(mid + 1,r,o << 1 | 1);
update(o);
}
int query(int l,int r,int o){
if(x <= l && r <= y){
return node[o].c;
}
int &mid = node[o].mid;
if(y <= mid)return query(l,mid,o << 1);
else if(x > mid)return query(mid + 1,r ,o << 1 | 1);
else {
return max(query(l,mid,o << 1),query(mid + 1,r,o << 1 | 1));
}
}
void modify(int l,int r,int o){
if(l == r && x == l){
node[o].c = y;return;
}
int &mid = node[o].mid;
if(x <= mid)modify(l,mid,o << 1);
else modify(mid + 1,r,o << 1 | 1);
update(o);
}
int go(int u,int v){
int f1 = top[u],f2 = top[v];
int ans = -1;
while(f1 != f2){
if(dep[f1] < dep[f2]){
swap(f1,f2);
swap(u,v);
}
x = id[f1],y = id[u];
ans = max(ans,query(1,cnt_node,1));
u = fa[f1];
f1 = top[u];
}
if(u == v)return ans;
if(dep[u] > dep[v])swap(u,v);
x = id[son[u]],y = id[v];//注意这里是son[u],这里每个点的边值相当于这个点和父亲的边的值,
//在这里当f1 == f2,且u != v时会发现u,v必定在同一条重链(轻链)中,那么应该是u的son的id才是线段树的左区间点
ans = max(ans,query(1,cnt_node,1));
return ans;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
init();
dfs1(1,0,0);
dfs2(1,0);
for(int i = 1;i < n;i++){
x = i - 1;
x = x << 1;
if(dep[e[x].u] < dep[e[x].v])swap(e[x].u,e[x].v);
ori[id[e[x].u]] = e[x].c;//这里将边权值强制赋值在点上,以点的深度更深的保存这条边的权值,这样利于在线段树上操作
}
build(1,cnt_node,1);
int a,b;
while(true){
scanf(" %s",ques);
if(ques[0] == 'D')break;
if(ques[0] == 'Q'){
scanf("%d%d",&a,&b);
printf("%d\n",go(a,b));
}else{
scanf("%d%d",&a,&y);
x = id[e[(a - 1) * 2].u];
modify(1,n,1);
}
}
}
}
上面这个树链剖分是基于边来建立树上的点的,具体来说,就是把每条边(u->v)的权值转化为v代表这条边的权值,由此可见,这棵树的根是没有权值的。所以在上面的代码的查询片段(go()函数中)中会出现。
x = id[son[u]],y = id[v];//注意这里是son[u],这里每个点的边值相当于这个点和父亲的边的值,
所以这里是son[u]而不是直接是u。
出现这样的情况是因为我们强制把边的权值加到了点上(方便加入线段树),如果题目本来就是在点上+,-某个值,查询两点之间点上的权值关系的话,就不用这样考虑了。那么其实上面的这段代码也可以去掉
if(u == v)return ans;
直接都当成同样的考虑,保存下面的代码就可以了。
如果是点修改的话,可以用如下的代码作参考:
/*
树链剖分,基于点
*/
#include
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 40005;
int n;
struct ppp{
int u,v,nex,c;
}e[maxn * 4];
int tole,head[maxn];
void make_edge(int u,int v){
e[tole].u = u;e[tole].v = v;e[tole].nex = head[u];head[u] = tole++;
}
int zhi[maxn];
int siz[maxn],fa[maxn],dep[maxn],son[maxn],id[maxn],top[maxn];
int cnt_node;//这个是边在线段树中的编号
void dfs1(int u,int pre,int depth){
dep[u] = depth;siz[u] = 1;son[u] = 0;fa[u] = pre;
for(int i = head[u];~i;i = e[i].nex){
int v = e[i].v;
if(v == fa[u])continue;
dfs1(v,u,depth + 1);
siz[u] += siz[v];
if(siz[son[u]] < siz[v])//找到重儿子
son[u] = v;
}
}
void dfs2(int u,int pre){
top[u] = pre;
id[u] = ++cnt_node;//给当前边在线段树上编号
if(son[u])dfs2(son[u],pre);//不是叶子节点的时候往重链走
for(int i = head[u];~i;i = e[i].nex){
int v = e[i].v;
if(v == fa[u] || v == son[u])continue;
dfs2(v,v);
}
}//至此所有重链轻链以编号并求出
void init(){
tole = cnt_node = 0;
for(int i = 0;i <= n;i++){
head[i] = -1;siz[i] = dep[i] = son[i] = id[i] = top[i] = 0;
}
for(int i = 1,a,b;i < n;i++){
scanf("%d%d",&a,&b);
make_edge(a,b);
make_edge(b,a);
}
for(int i = 1;i <= n;i++)scanf("%d",zhi + i);
}
char ques[50];
int x,y;
struct pp{
int l,r,mid,c,maxx;
pp(){}
void make(int _l,int _r){
l = _l,r = _r,mid = (l + r) >> 1;
maxx = -99999999;c = 0;
}
}node[maxn * 4];
int ori[maxn];
void update(int o){
node[o].maxx = max(node[o << 1].maxx,node[o << 1 | 1].maxx);
node[o].c = node[o << 1].c + node[o << 1 | 1].c;
}
void build(int l,int r,int o){
node[o].make(l,r);
if(l == r){
node[o].c = ori[l];
node[o].maxx = ori[l];
return;
}
int mid = (l + r) >> 1;
build(l,mid,o << 1);
build(mid + 1,r,o << 1 | 1);
update(o);
}
int query(int l,int r,int o){
if(x <= l && r <= y){
return node[o].maxx;
}
int &mid = node[o].mid;
if(y <= mid)return query(l,mid,o << 1);
else if(x > mid)return query(mid + 1,r ,o << 1 | 1);
else {
return max(query(l,mid,o << 1),query(mid + 1,r,o << 1 | 1));
}
}
int query1(int l,int r,int o){
if(x <= l && r <= y){
return node[o].c;
}
int &mid = node[o].mid;
if(y <= mid)return query1(l,mid ,o << 1);
else if(x > mid)return query1(mid + 1,r,o << 1 | 1);
else return query1(l,mid,o << 1) + query1(mid + 1,r,o << 1 | 1);
}
void modify(int l,int r,int o){
if(l == r && x == l){
node[o].c = y;
node[o].maxx = y;
return;
}
int &mid = node[o].mid;
if(x <= mid)modify(l,mid,o << 1);
else modify(mid + 1,r,o << 1 | 1);
update(o);
}
int go(int u,int v,int flag){
int f1 = top[u],f2 = top[v];
int ans = 0;
if(flag)ans = -999999999;
while(f1 != f2){
if(dep[f1] < dep[f2]){
swap(f1,f2);
swap(u,v);
}
x = id[f1],y = id[u];
if(flag == 1)
ans = max(ans,query(1,cnt_node,1));
else ans += query1(1,cnt_node,1);
u = fa[f1];
f1 = top[u];
}
if(dep[u] > dep[v])swap(u,v);
x = id[u],y = id[v];//注意这里是son[u],这里每个点的边值相当于这个点和父亲的边的值,
//在这里当f1 == f2,且u != v时会发现u,v必定在同一条重链(轻链)中,那么应该是u的son的id才是线段树的左区间点
if(flag)
ans = max(ans,query(1,cnt_node,1));
else ans += query1(1,cnt_node,1);
return ans;
}
int main(){
while(~scanf("%d",&n)){
init();
dfs1(1,0,0);
dfs2(1,0);
for(int i = 1;i <= n;i++){
ori[id[i]] = zhi[i];//这里将边权值强制赋值在点上,以点的深度更深的保存这条边的权值,这样利于在线段树上操作
}
build(1,cnt_node,1);
int a,b;
int q;
scanf("%d",&q);
while(q--){
scanf(" %s",ques);
if(ques[0] == 'Q'){
scanf("%d%d",&a,&b);
if(ques[1] == 'S'){
printf("%d\n",go(a,b,0));
}else {
printf("%d\n",go(a,b,1));
}
}else{
scanf("%d%d",&a,&y);
zhi[a] = y;
x = id[a];
modify(1,cnt_node,1);
}
}
}
}
再贴一下poj3237的代码:
#include
#include
#include
#include
#include
#include
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 20005;
const int INF = 999999999;
int n;
struct ppp{
int u,v,nex,c;
}e[maxn * 4];
int tole,head[maxn];
void make_edge(int u,int v,int c){
e[tole].u = u;e[tole].c = c;e[tole].v = v;e[tole].nex = head[u];head[u] = tole++;
}
int siz[maxn],fa[maxn],dep[maxn],son[maxn],id[maxn],top[maxn];
int cnt_node;//这个是边在线段树中的编号
void dfs1(int u,int pre,int depth){
dep[u] = depth;siz[u] = 1;son[u] = 0;fa[u] = pre;
for(int i = head[u];~i;i = e[i].nex){
int v = e[i].v;
if(v == fa[u])continue;
dfs1(v,u,depth + 1);
siz[u] += siz[v];
if(siz[son[u]] < siz[v])//找到重儿子
son[u] = v;
}
}
void dfs2(int u,int pre){
top[u] = pre;
id[u] = ++cnt_node;//给当前边在线段树上编号
if(son[u])dfs2(son[u],pre);//不是叶子节点的时候往重链走
for(int i = head[u];~i;i = e[i].nex){
int v = e[i].v;
if(v == fa[u] || v == son[u])continue;
dfs2(v,v);
}
}//至此所有重链轻链以编号并求出
void init(){
scanf("%d",&n);
tole = cnt_node = 0;
for(int i = 0;i <= n;i++){
head[i] = -1;siz[i] = dep[i] = son[i] = id[i] = top[i] = 0;
}
for(int i = 1,a,b,c;i < n;i++){
scanf("%d%d%d",&a,&b,&c);
make_edge(a,b,c);
make_edge(b,a,c);
}
}
char ques[50];
int x,y;
struct pp{
int l,r,mid,c,f,maxx,minn;
pp(){}
void make(int _l,int _r){
l = _l,r = _r,mid = (l + r) >> 1;f = 0;maxx = -INF;minn = INF;
}
}node[maxn * 4];
int ori[maxn];
void update(int o){
node[o].maxx = max(node[o << 1].maxx,node[o << 1 | 1].maxx);
node[o].minn = min(node[o << 1].minn,node[o << 1 | 1].minn);
}
void down(int o){
if(node[o].f){
node[o].f = 0;
node[o << 1].f = node[o << 1].f ^ 1;
node[o << 1 | 1].f = node[o << 1 | 1].f ^ 1;
swap(node[o << 1].maxx,node[o << 1].minn);
node[o << 1].maxx = -node[o << 1].maxx;
node[o << 1].minn = -node[o << 1].minn;
swap(node[o << 1 | 1].maxx,node[o << 1 | 1].minn);
node[o << 1 | 1].maxx = -node[o << 1 | 1].maxx;
node[o << 1 | 1].minn = -node[o << 1 | 1].minn;
}
}
void build(int l,int r,int o){
node[o].make(l,r);
if(l == r){
node[o].c = ori[l];
node[o].maxx = ori[l];
node[o].minn = ori[l];
return;
}
int mid = (l + r) >> 1;
build(l,mid,o << 1);
build(mid + 1,r,o << 1 | 1);
update(o);
}
int query(int l,int r,int o){
if(x <= l && r <= y){
return node[o].maxx;
}
int &mid = node[o].mid;
down(o);
if(y <= mid)return query(l,mid,o << 1);
else if(x > mid)return query(mid + 1,r ,o << 1 | 1);
else {
return max(query(l,mid,o << 1),query(mid + 1,r,o << 1 | 1));
}
}
void chage(int l,int r,int o){
if(x <= l && r <= y){
node[o].f ^= 1;
swap(node[o].maxx,node[o].minn);
node[o].maxx = -node[o].maxx;
node[o].minn = -node[o].minn;
return;
}
down(o);
int &mid = node[o].mid;
if(y <= mid)chage(l,mid,o << 1);
else if(x > mid)chage(mid + 1,r,o << 1 | 1);
else{
chage(l,mid,o << 1);
chage(mid + 1,r,o << 1 | 1);
}
update(o);
}
void modify(int l,int r,int o){
if(l == r && x == l){
node[o].c = y;
node[o].maxx = y;
node[o].minn = y;
return;
}
int &mid = node[o].mid;
down(o);
if(x <= mid)modify(l,mid,o << 1);
else modify(mid + 1,r,o << 1 | 1);
update(o);
}
int go(int u,int v,int flag){
int f1 = top[u],f2 = top[v];
int ans = -999999999;
while(f1 != f2){
if(dep[f1] < dep[f2]){
swap(f1,f2);
swap(u,v);
}
x = id[f1],y = id[u];
if(!flag)
ans = max(ans,query(1,cnt_node,1));
else chage(1,cnt_node,1);
u = fa[f1];
f1 = top[u];
}
if(u == v)return ans;
if(dep[u] > dep[v])swap(u,v);
x = id[son[u]],y = id[v];//注意这里是son[u],这里每个点的边值相当于这个点和父亲的边的值,
//在这里当f1 == f2,且u != v时会发现u,v必定在同一条重链(轻链)中,那么应该是u的son的id才是线段树的左区间点
if(!flag)
ans = max(ans,query(1,cnt_node,1));
else chage(1,cnt_node,1);
return ans;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
init();
dfs1(1,0,0);
dfs2(1,0);
for(int i = 1;i < n;i++){
x = i - 1;
x = x << 1;
if(dep[e[x].u] < dep[e[x].v])swap(e[x].u,e[x].v);
ori[id[e[x].u]] = e[x].c;//这里将边权值强制赋值在点上,以点的深度更深的保存这条边的权值,这样利于在线段树上操作
}
build(1,cnt_node,1);
int a,b;
while(true){
scanf(" %s",ques);
if(ques[0] == 'D')break;
if(ques[0] == 'Q'){
scanf("%d%d",&a,&b);
printf("%d\n",go(a,b,0));
}else if(ques[0] == 'C'){
scanf("%d%d",&a,&y);
x = id[e[(a - 1) * 2].u];
modify(1,n,1);
}else {
scanf("%d%d",&a,&b);
go(a,b,1);
}
}
}
}