简单dfs序 + 树链剖分

树链剖分

DFS序

先来讲一讲DFS序是什么东西,直接上图,方便理解。

估计巨巨们应该知道了DFS序的两个重要的东西,in,outin,outin,out数组。

  • ininin数组就是这个点进入DFS的时间。
  • outoutout数组就是这个点退出DFS递归栈的时间。
  • 这个时间要注意,当有点进入的时候才加,没有点进入的时候不加也不减。

所以我们发现一个节点的子树的遍历是将,刚好是区间[in,out][in, out][in,out],所以这里我们就可以通过线段树或者树状数组来维护其子树的节点权值以及节点权值的查询。

DFS序的简单问题

  • 操作一,对某个节点增加x的权值。
  • 操作二、对某个节点以及他的子树都增加x的权值。
  • 操作三、查询某个节点的权值。
  • 操作四、查询某个节点的子树的节点权值和。

对于这个问题,直接对应的就是线段树的单点、区间更新,以及单点、区间查询,直接上一个线段树就行。

DFS序简单应用题目 Apple Tree

思路

简单的dfsdfsdfs序的应用,然后用树状数组或者线段是维护区间就行,这里树状数组简单些,直接用树状数组了。

代码

// #include <bits/stdc++.h>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 1e5 + 10;int a[N], tree[N], n, m, tot;
int head[N], to[N << 1], nex[N << 1], cnt;
int in[N], out[N];char op[10];void add(int x, int y) {to[cnt] = y;nex[cnt] = head[x];head[x] = cnt++;
}void update(int pos, int x) {while(pos <= n) {tree[pos] += x;pos += (-pos) & (pos);}
}int query(int pos) {int ans = 0;while(pos) {// cout << pos << endl;ans += tree[pos];pos -= (-pos) & (pos);}return ans;
}void dfs(int rt, int fa) {in[rt] = ++tot;for(int i = head[rt]; ~i; i = nex[i])if(to[i] != fa)dfs(to[i], rt);out[rt] = tot;
}int main() {// freopen("in.txt", "r", stdin);while(scanf("%d", &n) != EOF) {memset(head, -1, sizeof head);memset(tree, 0, sizeof tree);tot = cnt = 0;int x, y;for(int i = 1; i < n; i++) {a[i] = 1;update(i, 1);scanf("%d %d", &x, &y);add(x, y);add(y, x);}dfs(1, 0);a[n] = 1;update(n, 1);scanf("%d", &m);for(int i = 0; i < m; i++) {scanf("%s %d", op, &x);if(op[0] == 'Q')    printf("%d\n", query(out[x]) - query(in[x] - 1));else {if(a[x])    update(in[x], -1), a[x] = 0;else    update(in[x], 1), a[x] = 1;}}}return 0;
}

树链剖分

我们先约定一些数组的意义。

fa记录当前Index节点的父节点编号
son记录当前Index节点的重儿子编号
sz记录当前节点的子树节点个数
dep记录当前节点的深度
top记录当前节点所在重链的最顶端的节点编号
rk记录dfs序为Index的节点编号
id记录当前Index节点的dfs序编号

第一次DFS

void dfs1(int rt, int f) {fa[rt] = f, sz[rt] = 1;//记录当前节点的父节点,子树大小。dep[rt] = dep[f] + 1;//记录当前节点的深度。for(int i = head[rt]; ~i; i = nex[i]) {if(to[i] == f)	continue;dfs1(to[i], rt);sz[rt] += sz[to[i]];//更新子树大小。if(!son[rt] || sz[to[i]] > sz[son[rt]])son[rt] = to[i];//如果当前节点没有重儿子,或者出现了一个节点的子树节点数量大于当前的重儿子的子树节点数量,则更新重儿子。}
}

上一幅图来理解dfs1更新的结果。

都是常规的更新,应该看图就懂了,唯一可能需要稍微一点点理解的就是son了。

第二次DFS

void dfs2(int rt, int t) {id[rt] = ++tot;rk[tot] = rt;top[rt] = t;//这个更新记录的就是当前节点所在重链的深度最小的节点。if(!son[rt])	return ;//如果没有重儿子,说明是叶节点,递归边界记得返回。dfs2(son[rt], t);//优先更新当前所在的重边,for(int i = head[rt]; ~i; i = nex[i]) {//更新其字节点的重边。if(to[i] == fa[rt] || to[i] == son[rt])	continue;dfs2(to[i], to[i]);//与重链间接相连的点一定是一条重链的top节点}
}

上一幅图来理解dfs2更新的结果,红色的圈起来的是每一条重链。

这张图是树链剖分的精髓,把所有的边划分成了,重边和轻边,并且所有的节点依旧持有DFS序的优秀性质,并且一条重链上的dfs序一定是连续的,因此我们同样可以用线段树等数据结构来维护和查询区间。

这里还有一个问题就是dfs2中为什么要先进行,对其重儿子的dfs然后再对其它的儿子进行dfs。

我们要保证top点的延续性,所以我们选择先进行重儿子的dfs。


接下来我们考虑两种操作如何实现

一、将树从x到y结点最短路径上所有节点的权值和

我们的第一想法就是能不能把这两个点,通过转换,变成最后在同一条重链上。

在我们上面记录的变量中,有fa当前节点的父节点编号,所以我们能做的大概好像只有把这个节点向上移动,并且我们每次移动可以跨越一整条重链,到与之相邻的另一条重链,同时我们还可以通过区间和的查询统计每次移动的花费。

从这个方向出发,我们考虑,什么时候哪个节点可以进行这样的操作。

每一次移动的dep[top]都会变小,如果我们让dep[top]更小的去进行这个操作,最后肯定是不可能到达,top = top的,所以我们只有一个选择,移动dep[top]小的节点,只有这样,这两个节点的top才有可能会相同,


举个例子,统计4 -> 6的最短路的节点编号和

显然dep[top[4]]=3<dep[top[6]]=1dep[top[4]] = 3 < dep[top[6]] = 1dep[top[4]]=3<dep[top[6]]=1,所以我们移动,节点4,统计sum(id[top[4]],id[4])=4sum(id[top[4]], id[4]) = 4sum(id[top[4]],id[4])=4,此时4到了2这个位置,我们发现top[2]=top[6]top[2] = top[6]top[2]=top[6],直接利用重链的dfs连续性,查询sum(id[2],id[6])=13sum(id[2], id[6]) = 13sum(id[2],id[6])=13,最后得到sumans=4+13=17sum_{ans} = 4 + 13 = 17sumans=4+13=17


二、将树从x到y结点最短路径上所有节点的权值加上Z

明白了上面的操作这个就简单了,无非是把区间查询改成区间更新就行了

树链剖分模板题

#include <bits/stdc++.h>
#define mid (l + r >> 1)
#define lson rt << 1, l, mid
#define rson rt << 1 | 1, mid + 1, r
#define ls rt << 1
#define rs rt << 1 | 1using namespace std;typedef long long ll;
const int N = 2e5 + 10;ll sum[N << 2], lazy[N << 2];
int head[N], value[N], nex[N << 1], to[N << 1], cnt;
int fa[N], sz[N], dep[N], id[N], rk[N], son[N], top[N], tot;
int n, m, mod;void dfs1(int rt, int f) {dep[rt] = dep[f] + 1;sz[rt] = 1; fa[rt] = f;for(int i = head[rt]; ~i; i = nex[i]) {if(to[i] == f)  continue;dfs1(to[i], rt);sz[rt] += sz[to[i]];if(!son[rt] || sz[to[i]] > sz[son[rt]])son[rt] = to[i];}
}void dfs2(int rt, int t) {top[rt] = t;id[rt] = ++tot;rk[tot] = rt;if(!son[rt])return ;dfs2(son[rt], t);for(int i = head[rt]; ~i; i = nex[i]) {if(to[i] == fa[rt] || to[i] == son[rt]) continue;dfs2(to[i], to[i]);}
}void updown(int rt, int l, int r) {if(lazy[rt]) {sum[ls] = (sum[ls] + (mid - l + 1) * lazy[rt] % mod) % mod;sum[rs] = (sum[rs] + (r - mid) * lazy[rt] % mod) % mod;lazy[ls] = (lazy[ls] + lazy[rt]) % mod;lazy[rs] = (lazy[rs] + lazy[rt]) % mod;lazy[rt] = 0;}
}void build(int rt, int l, int r) {if(l == r) {sum[rt] = value[rk[l]];return ;}build(lson);build(rson);sum[rt] = (sum[ls] + sum[rs]) % mod;
}ll query(int rt, int l, int r, int L, int R) {if(l >= L && r <= R)    return sum[rt];updown(rt, l, r);ll ans = 0;if(L <= mid)    ans += query(lson, L, R);if(R > mid)     ans += query(rson, L, R);return ans;
}void update(int rt, int l, int r, int L, int R, int k) {if(l >= L && r <= R) {sum[rt] = (sum[rt] + (r - l + 1) * k % mod) % mod;lazy[rt] = (lazy[rt] + k) % mod;return ;}updown(rt, l, r);if(L <= mid)    update(lson, L, R, k);if(R > mid)     update(rson, L, R, k);sum[rt] = (sum[ls] + sum[rs]) % mod;
}void print(int rt, int l, int r) {if(l == r) {printf("%lld\n", sum[rt]);return ;}updown(rt, l, r);print(lson);print(rson);
}void op1(int x, int y, int k) {while(top[x] != top[y]) {if(dep[top[x]] < dep[top[y]])   swap(x, y);update(1, 1, n, id[top[x]], id[x], k);x = fa[top[x]];}if(dep[x] > dep[y]) swap(x, y);update(1, 1, n, id[x], id[y], k);
}ll op2(int x, int y) {ll ans = 0;while(top[x] != top[y]) {if(dep[top[x]] < dep[top[y]])   swap(x, y);ans = (ans + query(1, 1, n, id[top[x]], id[x])) % mod;x = fa[top[x]];}if(dep[x] > dep[y]) swap(x, y);ans = (ans + query(1, 1, n, id[x], id[y])) % mod;return ans % mod;
}void op3(int x, int k) {update(1, 1, n, id[x], id[x] + sz[x] - 1, k);
}ll op4(int x) {return query(1, 1, n, id[x], id[x] + sz[x] - 1);
}void add(int x, int y) {to[cnt] = y;nex[cnt] = head[x];head[x] = cnt++;
}int main() {// freopen("in.txt", "r", stdin);// freopen("out.txt", "w", stdout);memset(head, -1, sizeof head), cnt = 0;int x, y, rt, op, z;scanf("%d %d %d %d", &n, &m, &rt, &mod);for(int i = 1; i <= n; i++)scanf("%d", &value[i]);for(int i = 1; i < n; i++) {scanf("%d %d", &x, &y);add(x, y);add(y, x);}dfs1(rt, 0);dfs2(rt, rt);// int fa[N], son[N], sz[N], id[N], rk[N], tp[N], dep[N], tot;// for(int i = 1; i <= n; i++)//     printf("%d %d %d %d %d %d\n", fa[i], son[i], sz[i], id[i], top[i], dep[i]);build(1, 1, n);// print(1, 1, n);// puts("");for(int i = 0; i < m; i++) {scanf("%d", &op);if(op == 1) {scanf("%d %d %d", &x, &y, &z);op1(x, y, z);}else if(op == 2) {scanf("%d %d", &x, &y);printf("%lld\n", op2(x, y) % mod);}else if(op == 3) {scanf("%d %d", &x, &y);op3(x, y);}else {scanf("%d", &x);printf("%lld\n", op4(x) % mod);}}// print(1, 1, n);return 0;
}

[JLOI2014]松鼠的新家

其实是个模板附赠题,比洛谷模板还简单,就一个操作,区间中每个节点 + 1,然后要注意在最后的答案中,除了第一个被访问的点,其他的ans都要减1处理,然后就完事了。

#include <bits/stdc++.h>
#define mid (l + r >> 1)
#define lson rt << 1, l, mid
#define rson rt << 1 | 1, mid + 1, r
#define ls rt << 1
#define rs rt << 1 | 1using namespace std;typedef long long ll;
const int N = 3e5 + 10;ll sum[N << 2], lazy[N << 2], ans[N];
int head[N], to[N << 1], nex[N << 1], cnt;
int fa[N], son[N], top[N], sz[N], dep[N], rk[N], id[N], tot;
int n, a[N];void add(int x, int y) {to[cnt] = y;nex[cnt] = head[x];head[x] = cnt++;
}void dfs1(int rt, int f) {fa[rt] = f, sz[rt] = 1;dep[rt] = dep[f] + 1;for(int i = head[rt]; ~i; i = nex[i]) {if(to[i] == f)  continue;dfs1(to[i], rt);sz[rt] += sz[to[i]];if(!son[rt] || sz[to[i]] > sz[son[rt]])son[rt] = to[i];}
}void dfs2(int rt, int t) {top[rt] = t;id[rt] = ++tot;rk[tot] = rt;if(!son[rt])    return ;dfs2(son[rt], t);for(int i = head[rt]; ~i; i = nex[i]) {if(to[i] == fa[rt] || to[i] == son[rt]) continue;dfs2(to[i], to[i]);}
}void push_down(int rt, int l, int r) {if(lazy[rt]) {lazy[ls] += lazy[rt];lazy[rs] += lazy[rt];sum[ls] += lazy[rt] * (mid - l + 1);sum[rs] += lazy[rt] * (r - mid);lazy[rt] = 0;}
}void update(int rt, int l, int r, int L, int R) {if(l >= L && r <= R) {lazy[rt] += 1;sum[rt] += r - l + 1;return ;}push_down(rt, l, r);if(L <= mid)    update(lson, L, R);if(R > mid)     update(rson, L, R);sum[rt] = sum[ls] + sum[rs];
}void query(int rt, int l, int r) {if(l == r) {ans[rk[l]] = sum[rt];return ;}push_down(rt, l, r);query(lson);query(rson);
}void update_tree(int x, int y) {while(top[x] != top[y]) {if(dep[top[x]] < dep[top[y]])   swap(x, y);update(1, 1, n, id[top[x]], id[x]);x = fa[top[x]];}if(dep[x] > dep[y]) swap(x, y);update(1, 1, n, id[x], id[y]); 
}int main() {// freopen("in.txt", "r", stdin);memset(head, -1, sizeof head);scanf("%d", &n);for(int i = 1; i <= n; i++)scanf("%d", &a[i]);int x, y;for(int i = 1; i < n; i++) {scanf("%d %d", &x, &y);add(x, y);add(y, x);}dfs1(1, 0);dfs2(1, 0);for(int i = 1; i < n; i++)update_tree(a[i], a[i + 1]);query(1, 1, n);for(int i = 2; i <= n; i++)ans[a[i]]--;for(int i = 1; i <= n; i++)printf("%lld\n", ans[i]);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/314608.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

#3864. Hero meet devil dp套dp + 状压 + 状态机

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给你一个只包含ACGTACGTACGT的串sss&#xff0c;再给你一个mmm&#xff0c;第iii行输出有多少个长度为mmm且只包含ACGTACGTACGT的串与sss的lcslcslcs为iii。 ∣s∣≤15,m≤1000|s|\le15,m\le1000∣s∣≤15,m…

WeihanLi.Npoi 导出支持自定义列内容啦

WeihanLi.Npoi 导出支持自定义列内容啦Intro之前也有网友给提出过希望列合并或者自定义列内容的 issue 或请求&#xff0c;起初因为自己做 WeihanLi.Npoi 这个扩展的最初目的是导入导出的简单化&#xff0c;使用这个扩展导出的 Excel/csv 可以直接导入成 List<TEntity>&a…

线段树优化的Dijkstra

板子 我测得的是这个东西在不开O2O2O2的情况下比pairpriorityqueuepair priority_queuepairpriorityq​ueue快了将近1/31/31/3 #include<bits/stdc.h> #define mid (l r >> 1) #define lson rt << 1, l, mid #define rson rt << 1 | 1, mid 1, ru…

P4590 [TJOI2018]游园会 dp套dp + 状态机

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给你一个长度为nnn的串sss&#xff0c;其只包含NOINOINOI三个字母&#xff0c;给你一个mmm&#xff0c;代表ttt串的长度&#xff0c;ttt串包含NOINOINOI三个字母但是不存在三个连续的NOINOINOI子串&#xff…

让 .Net 更方便的导入导出 Excel

让 .Net 更方便的导入导出ExcelIntro因为前一段时间需要处理一些 excel 数据&#xff0c;主要是导入/导出操作&#xff0c;将 Excel 数据转化为对象再用程序进行处理和分析&#xff0c;没有找到比较满意的库&#xff0c;于是就自己造了一个轮子&#xff0c;屏蔽掉了 xlsx 与 xl…

最大流,最小费用最大流:解析 + 各种板子

网络流初步 Edmond-Karp算法 网络流的基本概念 源点&#xff0c;这个点只有流量的流出&#xff0c;没有流入。汇点&#xff0c;这个点只有流量的流入&#xff0c;没有流出。容量&#xff0c;每条有向边的最大可承受的流的理论大小。流量&#xff0c;每条有向边的最大可承受的…

Codeforces Round #741 (Div. 2) D2. Two Hundred Twenty One (hard version) 前缀和 + 分段模型

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给你一个长度为nnn的串&#xff0c;代表111&#xff0c;−-−代表−1-1−1&#xff0c;让后有qqq个询问&#xff0c;每次询问[l,r][l,r][l,r]区间&#xff0c;将这段区间的数拿出来&#xff0c;设为a[1,r−l…

Zend创始人离职原来早已安排好

前边我们报导过&#xff0c;PHP 兼 Zend 公司联合创始人 Zeev Suraski 宣布目前已经从工作了 20 年的 Zend 公司离职。2 日晚间&#xff0c;知名 PHP 开发者&#xff0c;同时也是 PHP JIT 主力鸟哥&#xff08;惠新宸&#xff09;在朋友圈中曝出猛料&#xff1a;原来一切都是安…

Codeforces Round #643 (Div. 2)(A, B, C, D, E)

Codeforces Round #643 (Div. 2) Sequence with Digits 思路 一道暴力题&#xff0c;猜想在某一步一定会出现0&#xff0c;于是怀着忐忑提交了代码&#xff0c;结果还真的是这样。 代码 #include <bits/stdc.h>using namespace std;typedef long long ll;ll judge(l…

Codeforces Round #741 (Div. 2) E. Rescue Niwen! 字符串 + dp

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给你一个串sss&#xff0c;定义其扩张串为s1,s1s2,...,s1s2..sn,s2,s2s3,...,sns_1,s_1s_2,...,s_1s_2..s_n,s_2,s_2s_3,...,s_ns1​,s1​s2​,...,s1​s2​..sn​,s2​,s2​s3​,...,sn​&#xff0c;现在让…

梯度下降:求线性回归

梯度下降的直线拟合 实现说明 给定若干个x,yx, yx,y并且求得一个最佳的yaxby ax byaxb&#xff0c;也就是二元一次方程组的解。 先放上给定的散点&#xff0c;以及求得的线性回归的直线的图片。 我个人认为&#xff0c;这里的梯度优化&#xff0c;就是通过一个关键式子los…

Codeforces Round #268 (Div. 1) C. Hack it! 数位dp + 构造数位

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 定义f(x)f(x)f(x)表示xxx的十进制下数位和&#xff0c;现在给你aaa&#xff0c;让你选一个区间[l,r][l,r][l,r]&#xff0c;满足∑ilrf(i)moda0\sum_{il}^rf(i)\bmod a0∑ilr​f(i)moda0。 1≤a≤1e181\le a…

3分钟了解数据库事务

事务是由几个读取和修改数据的sql命令组成的&#xff0c;但是知道commit命令被执行之后&#xff0c;修改操作才被认为是正常的完成。显式事务常以Begin tran语句开头&#xff0c;以commit tran或者rollback tran语句结尾的。事务具有acid属性原子性&#xff08;atomicity&#…

D. The Best Vacation(贪心+前缀和+二分)

The Best Vacation 思路 前缀和加贪心 贪心&#xff1a;我们的结尾点一定是在某一个月的最后一天。 贪心部分证明&#xff1a;我们选定两组数 Aan−2,an−1,an,b1,b2,b3……bn−2,bn−1A a_{n - 2}, a_{n - 1}, a_{n}, b_{1}, b_{2}, b_{3}……b_{n - 2}, b_{n - 1}Aan−2…

P4396 [AHOI2013]作业 cdq分治

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给你一个长度为nnn的数列aaa&#xff0c;有qqq个询问&#xff0c;每次询问[l,r][l,r][l,r]中值域在[a,b][a,b][a,b]中的数出现的次数和在[a,b][a,b][a,b]中出现过的数值个数。 n≤1e5,1≤a≤1e5n\le1e5,1\le…

WebClient, HttpClient, HttpWebRequest ,RestSharp之间的区别与抉择

NETCore提供了三种不同类型用于生产的REST API&#xff1a; HttpWebRequest;WebClient;HttpClient&#xff0c;开源社区创建了另一个名为RestSharp的库。如此多的http库&#xff0c;该怎样选择呢&#xff1f;01HttpWebRequest这是.NET创建者最初开发用于使用HTTP请求的标准类。…

树的直径与重心

树的直径与重心 或许更好的阅读体验 树的直径求解方法一 思路 先选取一个点rt作为根节点&#xff0c;dfs去找到一个最长路径的点U&#xff0c;然后通过这个点去dfs&#xff0c;找到路径最长的点V&#xff0c;U->V就是这课树的直径。 证明正确性&#xff1a; 假如rt在直…

Codeforces Round #446 (Div. 1) B. Gluttony 构造 + 补集思想

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给你一个数组aaa&#xff0c;保证aaa中每个数都互不相同&#xff0c;让你构造一个数组bbb&#xff0c;满足对于任意的Sx1,x2,...,xk,1≤xi≤n,0≤k<nS{x_1,x_2,...,x_k},1\le x_i\le n,0\le k< nSx1​…

.NetCore 2.1以后的HttpClient最佳实践

ASP.NET Core 2.1中出现一个新的HttpClientFactory功能&#xff0c;它有助于解决开发人员在使用HttpClient实例从其应用程序发出外部Web请求时可能遇到的一些常见问题。01介绍在.NETCore平台的2.1新增了HttpClientFactory&#xff0c;虽然HttpClient这个类实现了disposable&…

D. Multiset(树状数组 + 二分)

Multiset 可能更好的阅读体验 思路 二分 树状数组做法 我们发现每个数的范围是$ < 1e6$的&#xff0c;所以可以直接在线操作&#xff0c;不用离散化离线操作。 这个时候我们的treetreetree数组就相当与一个桶&#xff0c;每个桶里统计的是值为其下标的个数&#xff0c…