学习前请先掌握线段树:线段树(维护区间信息)

一,思想:

将一颗树拆成多条线性链以方便维护(如线段树)。

先给出以下定义(通过这些定义我们就可以组成链):

  1. 重儿子:子节点最多的儿子就是重儿子
  2. 轻儿子:除了重儿子,其余都是轻儿子
  3. 重边:连接重儿子的边。
  4. 轻边:除了重边,其余都是轻边
  5. 重链:这条链上的边都是重边

1,建树:

在dfs1中我们需要遍历这颗树,建立每个点的父子关系,并统计每个点的子节点数,从而得出其重儿子

void dfs1(int u, int f)
{fa[u] = f;//存储u点父亲dep[u] = dep[f] + 1;//深度是父亲深度+1tot[u] = 1, son[u] = idx[u] = 0;//tot是子节点(包括自己)的数量,son是重儿子,idx是u的重新编号,对这3个初始化int maxn = -1;for (int i = head[u]; i; i = edge[i].next){int v = edge[i].to;if (v == f)continue;dfs1(v, u);tot[u] += tot[v];//增加u的子节点数if (tot[v] > maxn)maxn = tot[v], son[u] = v;//更新重儿子}
}

2,重新编号形成线性链

  1. 我们要对一条线性链操作,显然要求链上的点连续。因此我们需要给点重新编号。即从跟节点开始,每次优先给重儿子编号,这样这颗树最后就形成(同一条链是的点编号连续(因为优先给重儿子编号且优先以重儿子建链)并且子树内的点也是连续的)。
  2. 当然,我们需要知道链的端点,所以我们每个点都存储他所在链的顶点top。

我们dfs2实现的功能就是新编号,并记录每个点的top。观察上图我们发现,轻儿子就是一条新链的开端,所以他开新链时自己就是top

void dfs2(int u, int topfa)
{top[u] = topfa;//记录链顶点idx[u] = ++cnt;//新编号,从根节点的1不断编号a[cnt] = b[u];//把原来编号的值存入新编号的值if (!son[u])return;//如果没用儿子就不用往下dfs2(son[u], topfa);//有儿子先访问重儿子(重儿子优先编号)for (int i = head[u]; i; i = edge[i].next){int v = edge[i].to;if (!idx[v])dfs2(v, v);//idx为0,说明没用编号(也说明一定不是重儿子),进行编号,v自己是轻链顶点}
}

3,树上更新操作

更新x到y:

  1. 如果x,y是同一条链,那他们在连续区间内,直接更新。
  2. 如果不是,我们显然是更新完当前链(优先更新深度大的链,这样才能不断将两条链往上跳),然后跳到链上方的链进行更新(与lca一个原理),直到新x,y是同一条链,进行操作1。

void treeadd(int x, int y, ll z)
{while (top[x] != top[y])//不是同一条链,就更新到是为止{if (dep[top[x]] < dep[top[y]])swap(x, y);//为了方便,始终保持x所在链的顶点深度大(注意,比较的是顶点深度,不是x与y深度update(idx[top[x]], idx[x], 1, z);//链是连续区间,直接线段树更新x = fa[top[x]];//x成为顶点父亲}if (dep[x] > dep[y])swap(x, y);//出来后,为了方便,让x深度小,因为我们的线段树必须更新小编号到大编号update(idx[x], idx[y], 1, z);
}

4,树上查询

跟更新没什么区别

ll treeask(int x, int y)
{ll ans = 0;while (top[x] != top[y])//不是同一条链,就不断累加经过的链的区间的值{if (dep[top[x]] < dep[top[y]])swap(x, y);ans = (ans + ask(idx[top[x]], idx[x], 1)) % mod;x = fa[top[x]];}if (dep[x] > dep[y])swap(x, y);ans = (ans + ask(idx[x], idx[y], 1)) % mod;return ans % mod;
}

5,求lca,因为我们树链建好,也可以用于求lca,很显然两个点同步到同一条链(都是不断往祖先跳),此时谁深度小谁就是他们的LCA

ll lca(int u, int v)
{while (top[u] != top[v]){if (dep[top[u]] > dep[top[v]])u = fa[top[u]];//所在链顶点深度大的求往祖先跳else v = fa[top[v]];}return dep[u] > dep[v] ? v : u;
}

二,模板题:P3384 【模板】重链剖分/树链剖分

思路:

操作1,2上面都说过了。

操作3,4也很显然:我们说过,同一个子树是连续区间的点,如果父节点是x(线段树上编号idx[x]),那么树的最后一个点就是idx[x]+tot[x]-1。(tot[x]记录x子树上的节点数(包括x))。

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
#define int ll
const int N = 1e5 + 10;int n, m, r, mod, num, cnt;
int head[N << 1], tot[N], dep[N], fa[N], son[N], top[N], idx[N];
int a[N], b[N];
struct node
{int next, to;
} edge[N << 1];
struct tree
{int l, r;int sum, add;
} t[4 * N + 2];void add(int u, int v)
{edge[++num].next = head[u];edge[num].to = v;head[u] = num;
}void dfs1(int u, int f)
{fa[u] = f;//存储u点父亲dep[u] = dep[f] + 1;//深度是父亲深度+1tot[u] = 1, son[u] = idx[u] = 0;//tot是子节点(包括自己)的数量,son是重儿子,idx是u的重新编号,对这3个初始化int maxn = -1;for (int i = head[u]; i; i = edge[i].next){int v = edge[i].to;if (v == f)continue;dfs1(v, u);tot[u] += tot[v];//增加u的子节点数if (tot[v] > maxn)maxn = tot[v], son[u] = v;//更新重儿子}
}void dfs2(int u, int topfa)
{top[u] = topfa;//记录链顶点idx[u] = ++cnt;//新编号,从根节点的1不断编号a[cnt] = b[u];//把原来编号的值存入新编号的值if (!son[u])return;//如果没用儿子就不用往下dfs2(son[u], topfa);//有儿子先访问重儿子(重儿子优先编号)for (int i = head[u]; i; i = edge[i].next){int v = edge[i].to;if (!idx[v])dfs2(v, v);//idx为0,说明没用编号(也说明一定不是重儿子),进行编号,v自己是轻链顶点}
}//---------以下是线段树代码-------//
void build(int l, int r, int p)
{t[p].l = l, t[p].r = r;if (l == r){t[p].sum = a[l] % mod;return;}int mid = l + ((r - l) >> 1);build(l, mid, p << 1);build(mid + 1, r, p << 1 | 1);t[p].sum = (t[p << 1].sum + t[p << 1 | 1].sum) % mod;
}void lazy(int p)
{if (t[p].l == t[p].r)t[p].add = 0;if (t[p].add){t[p << 1].sum = (t[p << 1].sum + t[p].add * (t[p << 1].r - t[p << 1].l + 1)) % mod;t[p << 1 | 1].sum = (t[p << 1 | 1].sum + t[p].add * (t[p << 1 | 1].r - t[p << 1 | 1].l + 1)) % mod;t[p << 1].add = (t[p << 1].add + t[p].add) % mod;t[p << 1 | 1].add = (t[p << 1 | 1].add + t[p].add) % mod;t[p].add = 0;}
}void update(int l, int r, int p, ll z)
{if (l <= t[p].l && t[p].r <= r){t[p].sum = (t[p].sum + z * (t[p].r - t[p].l + 1)) % mod;t[p].add = (t[p].add + z) % mod;return;}lazy(p);int mid = t[p].l + ((t[p].r - t[p].l) >> 1);if (l <= mid)update(l, r, p << 1, z);if (r > mid)update(l, r, p << 1 | 1, z);t[p].sum = (t[p << 1].sum + t[p << 1 | 1].sum) % mod;
}ll ask(int l, int r, int p)
{if (l <= t[p].l && t[p].r <= r)return t[p].sum % mod;lazy(p);int mid = t[p].l + ((t[p].r - t[p].l) >> 1);ll ans = 0;if (l <= mid)ans = (ans + ask(l, r, p << 1)) % mod;if (r > mid)ans = (ans + ask(l, r, p << 1 | 1)) % mod;return ans;
}
//------以上是线段树代码------//void treeadd(int x, int y, ll z)
{while (top[x] != top[y])//不是同一条链,就更新到是为止{if (dep[top[x]] < dep[top[y]])swap(x, y);//为了方便,始终保持x所在链的顶点深度大(注意,比较的是顶点深度,不是x与y深度update(idx[top[x]], idx[x], 1, z);//链是连续区间,直接线段树更新x = fa[top[x]];//x成为顶点父亲}if (dep[x] > dep[y])swap(x, y);//出来后,为了方便,让x深度小,因为我们的线段树必须更新小编号到大编号update(idx[x], idx[y], 1, z);
}ll treeask(int x, int y)
{ll ans = 0;while (top[x] != top[y])//不是同一条链,就不断累加经过的链的区间的值{if (dep[top[x]] < dep[top[y]])swap(x, y);ans = (ans + ask(idx[top[x]], idx[x], 1)) % mod;x = fa[top[x]];}if (dep[x] > dep[y])swap(x, y);ans = (ans + ask(idx[x], idx[y], 1)) % mod;return ans % mod;
}
//这模板题用不到
ll lca(int u, int v)
{while (top[u] != top[v]){if (dep[top[u]] > dep[top[v]])u = fa[top[u]];//所在链顶点深度大的求往祖先跳else v = fa[top[v]];}return dep[u] > dep[v] ? v : u;
}int32_t main()
{cin >> n >> m >> r >> mod;for (int i = 1; i <= n; ++i)cin >> b[i];int h, x, y, z;for (int i = 1; i < n; ++i){cin >> x >> y;add(x, y), add(y, x);}dfs1(r, 0);dfs2(r, r);build(1, n, 1);while (m--){cin >> h;if (h == 1){cin >> x >> y >> z;treeadd(x, y, z);}else if (h == 2){cin >> x >> y;cout << treeask(x, y) << endl;}else if (h == 3){cin >> x >> z;update(idx[x], idx[x] + tot[x] - 1, 1, z);}else if (h == 4){cin >> x;cout << ask(idx[x], idx[x] + tot[x] - 1, 1) << endl;}}return 0;
}

三:例题:The LCIS on the Tree

思路:

很考验细节...

重点就是对区间合并时的操作与区间的维护

我们对于一段区间需要维护

fl:左降序,sl:左升序,fr:右降序,sr:右升序

左右端点值:lnum,rnum

区间边界:l,r

区间降序最大长度:fmaxn,区间升序最大长度:smaxn

区间节点数:size(用于决定该区间是否为空)

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
const int N = 1e5 + 10;int a[N], b[N], top[N], tot[N], dep[N], head[N], son[N], fa[N], idx[N];
int num, cnt, n, m;struct node
{int next, to;
} edge[N];struct tree
{int l, r, lnum, rnum, size;int fl, fr, sl, sr;int fmaxn, smaxn;void init(){lnum = rnum = fl = fr = sl = sr = size = fmaxn = smaxn = 0;}void reverse()//用于把这个tree区间左右信息互换{swap(fl, sr), swap(sl, fr), swap(lnum, rnum);swap(fmaxn, smaxn);}
} t[4 * N];void add(int u, int v)
{edge[++num].next = head[u];edge[num].to = v;head[u] = num;
}
void init()
{memset(head, 0, sizeof(head));num = cnt = 0;
}void dfs1(int u, int f)
{fa[u] = f;dep[u] = dep[f] + 1;tot[u] = 1, son[u] = idx[u] = 0;int maxn = -1;for (int i = head[u]; i; i = edge[i].next){int v = edge[i].to;if (v == f)continue;dfs1(v, u);tot[u] += tot[v];if (tot[v] > maxn)maxn = tot[v], son[u] = v;}
}void dfs2(int u, int topfa)
{top[u] = topfa;idx[u] = ++cnt;a[cnt] = b[u];if (!son[u])return;dfs2(son[u], topfa);for (int i = head[u]; i; i = edge[i].next){int v = edge[i].to;if (!idx[v])dfs2(v, v);}
}tree unit(tree l, tree r)
{if (!l.size)return r;//如果左树没用节点(为空),那直接放回右部分即可if (!r.size)return l;//同理tree t;t.l = l.l, t.r = r.r;t.size = l.size + r.size;//sized等于左边加右边t.lnum = l.lnum, t.rnum = r.rnum;t.fl = l.fl;if (l.fl == l.size && l.rnum > r.lnum)t.fl += r.fl;t.sl = l.sl;if (l.sl == l.size && l.rnum < r.lnum)t.sl += r.sl;t.fr = r.fr;if (r.fr == r.size && l.rnum > r.lnum)t.fr += l.fr;t.sr = r.sr;if (r.sr == r.size && l.rnum < r.lnum)t.sr += l.sr;
//以降序fmaxn为例,更新时要么去左边的fmaxn,要么右边的fmaxn,要么如果两边符合端点l.rnum > r.lnum,则可以多算中间一段t.fmaxn = max(l.fmaxn, max(r.fmaxn, (l.rnum > r.lnum ? l.fr + r.fl : 0)));t.smaxn = max(l.smaxn, max(r.smaxn, (l.rnum < r.lnum ? l.sr + r.sl : 0)));return t;
}void change(int p)
{t[p] = unit(t[p << 1], t[p << 1 | 1]);
}void build(int l, int r, int p)
{t[p].l = l, t[p].r = r;if (l == r){t[p].lnum = t[p].rnum = a[l];t[p].size = t[p].fmaxn = t[p].smaxn = t[p].fl = t[p].sl = t[p].sr = t[p].fr = 1;return ;}int mid = l + ((r - l) >> 1);build(l, mid, p << 1);build(mid + 1, r, p << 1 | 1);change(p);
}tree ask(int l, int r, int p)
{if (l <= t[p].l && t[p].r <= r)return t[p];int mid = t[p].l + ((t[p].r - t[p].l) >> 1);//如果l,r范围只在左右区间一边,直接返回那一边得到的tree即可if (r <= mid)return ask(l, r, p << 1);if (l > mid)return ask(l, r, p << 1 | 1);tree t, tl, tr;tl = ask(l, r, p << 1), tr = ask(l, r, p << 1 | 1);return (t = unit(tl, tr));
}int treeask(int x, int y)
{bool flag = 0;tree l, r;//我们刚刚开始tree是空的,记得初始化,否则与别人连接出问题l.init();r.init();while (top[x] != top[y]){if (dep[top[x]] < dep[top[y]])swap(x, y), swap(l, r), flag ^= 1;//x,y每次翻转记录一下tree tmp = ask(idx[top[x]], idx[x], 1);l = unit(tmp, l);//越往上的段在左边x = fa[top[x]];}if (dep[x] > dep[y])swap(x, y), swap(l, r), flag ^= 1;tree tmp = ask(idx[x], idx[y], 1);//x深度小于y,则这一段跟y的链拼接r = unit(tmp, r);if (flag)swap(l, r);//如果翻转奇数次,那么我们最后得出结果是y->x,所以我们需要否则回来l.reverse();//最后是x的顶端拼接y的端点,显然需要将x左右翻转return unit(l, r).smaxn;
}int main()
{std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);int t, x, y;cin >> t;for (int ti = 1; ti <= t; ++ti){init();cin >> n;for (int i = 1; i <= n; ++i)cin >> b[i];for (int i = 2; i <= n; ++i){cin >> x;add(x, i);}dfs1(1, 0);dfs2(1, 1);build(1, n, 1);cin >> m;cout << "Case #" << ti << ":" << endl;while (m--){cin >> x >> y;cout << treeask(x, y) << endl;}if (ti < t)cout << endl;}return 0;
}

例题2:Query on a tree

思路:树链+线段树离线处理

我们考虑如果用在线的操作,先给点赋值,然后对于每个问题,我们一个一个去查询。那我们查一个问题最坏有可能几乎查了每一个点(只要我一直找不到小于y的max值的话)!(那我建线段树来logn查询还有什么意义呢(笑))

我们反思一下,我明明找小于y的边,你们这些大于他的边凑什么热闹,滚一边去。——于是,想到了离线处理。

  1. 我们想要查找小于y的边时,保证图上只建立了小于y的边。
  2. 显然,我们首先给边排个序,当然,y们也要排个序。
  3. 这样,一开始先建个空树。然后边权给深度较深的点。
  4. 我们每次查询(x,y)前,在线段树是把点值小于等于y的更新号,然后查询即可。
#include <bits/stdc++.h>
using namespace std;
#define ll     long long
const int N = 1e5 + 10;int head[N], tot[N], dep[N], idx[N], top[N], fa[N], son[N];
int num, cnt;
int ans[N];struct tree
{int l, r;int max;
} t[N << 2];
struct node
{int next, to;
} edge[N << 1];
struct dot
{int x, y, w, id;bool operator<(const dot&k)const{return w < k.w;}
} v1[N], v2[N];void init()
{memset(head, 0, sizeof(head));num = cnt = 0;
}void add(int u, int v)
{edge[++num].next = head[u];edge[num].to = v;head[u] = num;
}void dfs1(int u, int f)
{dep[u] = dep[f] + 1;fa[u] = f;tot[u] = 1;idx[u] = son[u] = 0;int maxn = -1;for (int i = head[u]; i; i = edge[i].next){int v = edge[i].to;if (v == f)continue;dfs1(v, u);tot[u] += tot[v];if (tot[v] > maxn)maxn = tot[v], son[u] = v;}
}
void dfs2(int u, int topfa)
{top[u] = topfa;idx[u] = ++cnt;if (!son[u])return;dfs2(son[u], topfa);for (int i = head[u]; i; i = edge[i].next){int v = edge[i].to;if (!idx[v])dfs2(v, v);}
}void build(int l, int r, int p)
{t[p].l = l, t[p].r = r, t[p].max = -1;if (l == r)return;int mid = l + ((r - l) >> 1);build(l, mid, p << 1);build(mid + 1, r, p << 1 | 1);
}void update(int id, int p, int w)//更新线段树是id这个点
{if (t[p].l == id && t[p].r == id){t[p].max = w;return;}int mid = t[p].l + ((t[p].r - t[p].l) >> 1);if (id <= mid)update(id, p << 1, w);if (id > mid)update(id, p << 1 | 1, w);t[p].max = max(t[p << 1].max, t[p << 1 | 1].max);
}int ask(int l, int r, int p)
{if (l <= t[p].l && t[p].r <= r)return t[p].max;int mid = t[p].l + ((t[p].r - t[p].l) >> 1);int ans = -1;if (l <= mid)ans = max(ans, ask(l, r, p << 1));if (r > mid)ans = max(ans, ask(l, r, p << 1 | 1));return ans;
}
int treeask(int x, int y)
{int ans = -1;while (top[x] != top[y]){if (dep[top[x]] < dep[top[y]])swap(x, y);ans = max(ans, ask(idx[top[x]], idx[x], 1));x = fa[top[x]];}if (dep[x] > dep[y])swap(x, y);ans = max(ans, ask(idx[x], idx[y], 1));return ans;
}int main()
{std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);int t;cin >> t;while (t--){init();int n, q;cin >> n;for (int i = 1; i < n; ++i){cin >> v1[i].x >> v1[i].y >> v1[i].w;add(v1[i].x, v1[i].y), add(v1[i].y, v1[i].x);}cin >> q;for (int i = 1; i <= q; ++i)    cin >> v2[i].x >> v2[i].w, v2[i].id = i;//因为排序打乱原理q的顺序,id记录其原始顺序dfs1(1, 0);dfs2(1, 1);build(1, n, 1);//建空树sort(v1 + 1, v1 + n);//按小到大排点值sort(v2 + 1, v2 + 1 + q);//排查询的y值int cnt1 = 1;//指针表示当前更新了前cnt1个点for (int i = 1; i <= q; ++i){while (v1[cnt1].w <= v2[i].w && cnt1 < n)//每次查询y前更新小于y的点{int x = v1[cnt1].x, y = v1[cnt1].y, w = v1[cnt1].w;if (dep[x] < dep[y])swap(x, y);//点值给边两端深度较深的点update(idx[x], 1, w);cnt1++;}ans[v2[i].id] = treeask(1, v2[i].x);}for (int i = 1; i <= q; ++i)cout << ans[i] << endl;}return 0;
}

例题3:Relief grain

思路:树链+权值线段树+树上差分

  1. 首先,我们需要维护每个点拥有的各种粮食及其数量,还有最大值。显然用权值线段树维护较方便    权值线段树
  2. 每次对x到y路径上的点分发食物w,显然用树上差分标记起始与终点最后再合并更方便。

问题是,我们能不能做到差分数组合并与每个点的维护同步呢?

  1. 观察到,我们的树链对每个点重新编号后,序号是与差分数组一样的。所以我们本来需要每个点都建立权值线段树维护,但是最终只需要一颗树从头到尾维护即可。
  2. 我们从根点1开始:
    1. 首先,把他差分数组加的粮食更新到权值线段树。这样当前权值线段树的最大值就是点1的答案。
    2. 接着我们到点2,差分数组是继承1点的数据,所以权值线段树用原来1点的维护即可。
    3. 然后3点.........
#include <bits/stdc++.h>
using namespace std;
#define ll     long long
const int N = 1e5 + 10;
int head[N], tot[N], dep[N], top[N], son[N], fa[N], back[N], idx[N], ans[N];
int num, cnt;
int n, m;
vector<int>v[N];
struct tree
{int l, r;int cnt, max;//cnt表示区间数量最多的数字的数量,max表示数量最多的数字
} t[N << 2];
struct node
{int next, to;
} edge[N << 1];void init()
{memset(head, 0, sizeof(head));for (int i = 1; i <= n + 1; ++i)v[i].clear();//因为差分的原因(尾点+1),我们实际更新了n+1个点cnt = num = 0;
}void add(int u, int v)
{edge[++num].next = head[u];edge[num].to = v;head[u] = num;
}void dfs1(int u, int f)
{dep[u] = dep[f] + 1;fa[u] = f;tot[u] = 1;idx[u] = son[u] = 0;int maxn = -1;for (int i = head[u]; i; i = edge[i].next){int v = edge[i].to;if (v == f)continue;dfs1(v, u);tot[u] += tot[v];if (tot[v] > maxn)maxn = tot[v], son[u] = v;}
}void dfs2(int u, int topfa)
{top[u] = topfa;idx[u] = ++cnt;back[cnt] = u;if (!son[u])return;dfs2(son[u], topfa);for (int i = head[u]; i; i = edge[i].next){int v = edge[i].to;if (!idx[v])dfs2(v, v);}
}void build(int l, int r, int p)
{if (l > r)return;t[p].l = l, t[p].r = r, t[p].cnt = 0;//初始化if (l == r){t[p].max = l;return;}int mid = l + ((r - l) >> 1);build(l, mid, p << 1);build(mid + 1, r, p << 1 | 1);
}void update(int id, int p, int w)
{if (t[p].l == id && id == t[p].r){t[p].cnt += w;return;}int mid = t[p].l + ((t[p].r - t[p].l) >> 1);if (mid >= id)update(id, p << 1, w);if (mid < id)update(id, p << 1 | 1, w);if (t[p << 1].cnt >= t[p << 1 | 1].cnt)t[p].cnt = t[p << 1].cnt, t[p].max = t[p << 1].max;else t[p].cnt = t[p << 1 | 1].cnt, t[p].max = t[p << 1 | 1].max;
}void treeupdate(int x, int y, int w)
{while (top[x] != top[y]){if (dep[top[x]] < dep[top[y]])swap(x, y);v[idx[top[x]]].push_back(w);v[idx[x] + 1].push_back(-w);x = fa[top[x]];}if (dep[x] > dep[y])swap(x, y);v[idx[x]].push_back(w), v[idx[y] + 1].push_back(-w);//v数组存储每个点的差分信息
}int main()
{std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);while (cin >> n >> m && (n || m)){init();int x, y, w, maxn = 0;for (int i = 1; i < n; ++i)cin >> x >> y, add(x, y), add(y, x);dfs1(1, 0);dfs2(1, 1);while (m--){cin >> x >> y >> w;treeupdate(x, y, w);//存储树上差分maxn = max(maxn, w);}t[1].cnt = 0;build(1, maxn, 1);//建立空的权值线段树for (int i = 1; i <= n; ++i)//从1开始维护{for (int j = 0; j < (int)v[i].size(); ++j){if (v[i][j] > 0)update(v[i][j], 1, 1);else update(-v[i][j], 1, -1);//权值为负,即是食物w分配区间的尽头,之后的点要减少了}ans[back[i]] = 0;if (t[1].cnt > 0)ans[back[i]] = t[1].max;//只有cnt大于0,才说明有食物}for (int i = 1; i <= n; ++i)cout << ans[i] << endl;}return 0;
}

树链剖分(维护树上信息)相关推荐

  1. jzoj3626-[LNOI2014]LCA【树链剖分,线段树】

    正题 题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3626 题目大意 一棵树,每次给出(l,r,z)(l,r,z)(l,r,z)询问∑i ...

  2. 对LCA、树上倍增、树链剖分(重链剖分长链剖分)和LCT(Link-Cut Tree)的学习

    LCA what is LCA & what can LCA do LCA(Lowest Common Ancestors),即最近公共祖先 在一棵树上,两个节点的深度最浅的公共祖先就是 L ...

  3. [ZJOI2015] 幻想乡战略游戏(树链剖分 + 线段树二分 + 带权重心)

    problem luogu-P3345 solution 这是一个带权重心的题,考察动态点分治.点分治?呵,不可能的,这辈子都不可能写点分治 我们重新考虑重心的性质:以这个点为根时,所有子树的大小不会 ...

  4. [NOI2021 day1]轻重边(树链剖分),路径交点(矩阵行列式)

    NOI 2021 day1 轻重边 description solution code 路径交点 description solution code 轻重边 description solution ...

  5. YbtOJ-交换游戏【树链剖分,线段树合并】

    正题 题目大意 给出两棵树,对于第一棵树的每一条边(x,y)(x,y)(x,y)询问有多少条在第二棵树上的边(u,v)(u,v)(u,v)与其交换(连接的序号相同)后两棵树依旧是一棵树. 1≤n≤2× ...

  6. YbtOJ-染色计划【树链剖分,线段树,tarjan】

    正题 题目大意 给出nnn个点的一棵树,每个点有个颜色aia_iai​,你每次可以选择一个颜色全部变成另一个颜色. 求最少多少次操作可以把一种颜色变成一个完整的连通块. 1≤k≤n≤2×1051\le ...

  7. 小清的树链剖分10题日志01 树链剖分种果子 有你好果子吃的

    声明:由于本人能力尚不优 故无法做出解释文章 此文仅为自己日记 感谢你的阅读 作为队里的数据结构选手 2019西安邀请赛的E题 竟然在有机时的情况下 想到了线段树log^2的拆位做法 但是题目路径把自 ...

  8. 洛谷P4338 [ZJOI2018]历史(LCT,树形DP,树链剖分)

    洛谷题目传送门 ZJOI的考场上最弱外省选手T2 10分成功滚粗...... 首先要想到30分的结论 说实话Day1前几天刚刚刚掉了SDOI2017的树点涂色,考场上也想到了这一点 想到了又有什么用? ...

  9. P6805-[CEOI2020]春季大扫除【贪心,树链剖分,线段树】

    正题 题目链接:https://www.luogu.com.cn/problem/P6805 题目大意 给出nnn个点的一棵树,qqq次独立的询问.每次询问会在一些节点上新增一些子节点,然后你每次可以 ...

  10. hdu5111 树链剖分,主席树

    hdu5111 链接 hdu 思路 先考虑序列上如何解决. 1 3 2 5 4 1 2 4 5 3 这个序列变成 1 2 3 4 5 1 3 5 5 2 是对答案没有影响的(显然). 然后查询操作\( ...

最新文章

  1. 【PHPWord】插入Excel对象
  2. 上市后首份年报亮眼,快手天花板在哪?
  3. idea部署web项目,能访问jsp,访问Servlet却出现404错误的解决方法汇总
  4. JZOJ 5398. 【NOIP2017提高A组模拟10.7】Adore
  5. 为游戏开发者总结的20个 Unity 建议和技巧
  6. 《Hadoop大明白》【1】Hadoop的核心组件
  7. CREO - 基础2 - 如何让装配好的零件重新装配
  8. 安卓学习笔记17:常用控件 - 编辑框
  9. CSS3产生渐变效果
  10. 大一计算机引论知识点,计算机引论知识点2013-1-6.doc
  11. 江苏省×××局数据复制软件招标
  12. java找不到符号解决办法
  13. 【DirectX11】【学习笔记(10)】混合
  14. RVDS 3.1 下载地址及破解方法
  15. 为何戴绿帽的总是悲催老实人?
  16. 键盘连不上计算机,电脑键盘连接不上电脑是怎么回事
  17. Subversion的安装部署与用户验证配置
  18. Third1: Basic Web applications BASIC NFS services triggering mount | Cloud computing
  19. java.lang.IllegalArgumentException 异常报错完美解决
  20. 微信网页授权的2种方式

热门文章

  1. 数据采集实验-爬取李开复博客并保存在csv和mongodb中
  2. 请各位博友对Hyper-V的运用终了指摘
  3. 带宽、网速和流量之间的关系
  4. USB转串口设备linux随笔
  5. 微信小程序封装multipart请求体(wx-formdata)
  6. 深度挖掘文物价值,VR博物馆讲好文物故事
  7. 练手项目之会议室预订
  8. fastjson已经导入到项目中但还是提示:java.lang.NoClassDefFoundError: com/alibaba/fastjson/JSON
  9. 基于python的pca实现(附代码)
  10. 施密特正交化及QR分解(附实现代码)