今天抽了些时间来看树链剖分,复习了一下板子(终于过了QAQ)
所谓树链剖分, 就是将一颗张牙舞爪的树剖成一条条单链, 再将单链套上线段树或树状数组进行各种操作, 可以看下这个博客对树链剖分的基础理解 戳这里
复杂度为 O(nlog2n) O ( n l o g 2 n )
那么我们考虑一下进行这样一次剖分需要些什么。
首先,我们需要知道每一个节点的父亲节点, 否则无法得知上一条链的状况。重儿子也必须同时确定, 否则无法把一条链完整地剖下来。同时我们也需要知道每个点距离树根的深度以及当前点子节点的总个数,以便确定是否是重链。
于是我们有了这样一段代码。
int dfs1(int now, int fa, int deep)
{
dep[now] = deep;//记录深度
fat[now] = fa;//记录父节点
tot[now] = 1;//记录子树大小
int ms = -1;//重儿子的深度
for (int i = head[now]; i != -1; i = edge[i].nex)
{
if(edge[i].to == fa) continue;
//因为建的是双向边,所以可能又回到父节点,要舍掉
tot[now] += dfs1(edge[i].to, now, deep + 1);
if (tot[edge[i].to] > ms)
ms = tot[edge[i].to], son[now] = edge[i].to;
}
return tot[now];
}
有了这些, 我们就可以方便地将重新分配树上对应点在线段树上的下标。
void dfs2(int now, int topf)
{
idx[now] = ++id; //使用idx分配新的下标
deal[idx[now]] = pre[now]; //deal存分配后的数据,pre为输入数据
top[now] = topf;//记录当前节点所在链的最上端
if(!son[now]) return;//无儿子则返回
dfs2(son[now], topf);//先沿着重儿子剖下最长的一条链
for (R int i = head[now]; i != -1; i = edge[i].nex)
{
if(!idx[edge[i].to]) //非父节点方向
dfs2(edge[i].to, edge[i].to);//将这个节点作为新的顶端剖下来
}
}
分配好新的下标就可以开始愉快地建树啦
IN void pushup(const int &now)//与普通线段树无异
{
tree[now].val = (ls.val + rs.val + MOD) % MOD;
}
void build(int now, int l, int r)
{
tree[now].lef = l, tree[now].rig = r, tree[now].siz = r - l + 1;
if(l == r) {tree[now].val = deal[l];return;}//获取分配后的值
int mid = (l + r) >> 1;
build(now << 1, l, mid);
build(now << 1 | 1, mid + 1, r);
pushup(now);
}
IN void pushdown(const int &now)//普通的lazy标记下传操作
{
if (!tree[now].del) return;
ls.val += tree[now].del * ls.siz; ls.val %= MOD;
rs.val += tree[now].del * rs.siz; rs.val %= MOD;
ls.del += tree[now].del; ls.del %= MOD;
rs.del += tree[now].del; rs.del %= MOD;
tree[now].del = 0;
}
那么, 在建好树后, 我们该如何实现修改呢?
对于一条链上的修改, 我们只需要在线段树上修改一个连续的区间即可, 因为我们在剖链时就是按照重链->轻链的顺序进行的。而这个区间的左右范围我们可以根据idx值来确定。
而在几条链上的修改, 我们则需要先改完一条链上的, 再跳至上一条链上继续修改。此时我们的fat数组和top数组就有用处了, 它们可以帮助我们找到上一条链继续修改的位置。
void add(const int &now, const int &lb, const int &rb, const int &delta)
{
if(tree[now].lef >= lb && tree[now].rig <= rb)
{
tree[now].del += delta;
tree[now].val += tree[now].siz * delta;
return;
}
pushdown(now);
int mid = (tree[now].lef + tree[now].rig) >> 1;
if(lb <= mid) add(now << 1, lb, rb, delta);
if(rb > mid) add(now << 1 | 1, lb, rb, delta);
pushup(now);
}
IN void tree_add(int x, int y, const int &delta)
{
W (top[x] != top[y])//通过top数组确定是否在同一条链上
{
if(dep[top[x]] < dep[top[y]]) swap(x, y);//从更深的开始处理
add(1, idx[top[x]], idx[x], delta);
x = fat[top[x]];//转至上一条链的对应处
}
if(dep[x] > dep[y]) swap(x, y);
add(1, idx[x], idx[y], delta);//处理最后一条链上时的一部分
}
查询操作也大同小异, 故一同贴出…
int query(const int &now, const int &lb, const int &rb)
{
int ans = 0;
if(tree[now].lef >= lb && tree[now].rig <= rb) return tree[now].val;
pushdown(now);
int mid = (tree[now].lef + tree[now].rig) >> 1;
if(lb <= mid) ans += query(now << 1, lb, rb) % MOD;
ans %= MOD;
if(rb > mid) ans += query(now << 1 | 1, lb, rb) % MOD;
ans %= MOD;
return ans;
}
IN int tree_sum(int x, int y)
{
R int rt = 0;
W (top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
rt = (rt + query(1, idx[top[x]], idx[x])) % MOD;
x = fat[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
rt = (rt + query(1, idx[x], idx[y])) % MOD;
return rt;
}
接下来说说坑点。
1.洛谷上写的数据范围是1e5,但各数组开20w会TLE!!注意是TLE不是RE!!博主为此调了2个小时….最后偶然把数据范围调成了50w就过了…
2.注意随时取模, 否则很有可能答案溢出int,实测long long速度感人…
3.查询与修改部分的边界条件注意不要手贱打错。
4.修改时注意也要pushdown。
5.注意树上查询时两个取等方向相反 , 否则莫名WA…所以不要抄自己的前面打出的程序…