简单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,一经查实,立即删除!

相关文章

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

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

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

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

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

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

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

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

梯度下降:求线性回归

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

3分钟了解数据库事务

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

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

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

.NetCore 2.1以后的HttpClient最佳实践

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

点分治(简要讲解 + 模板)

树上点分治 思想 两个点之间的距离无非就是两种关系&#xff1a;我们约定dis[i]dis[i]dis[i]表示这个点到当前根节点的距离 dis[u]dis[v]dis[u] dis[v]dis[u]dis[v]&#xff0c;在同一个根节点的不同子树上。dis[u]dis[v]dis[u] dis[v]dis[u]dis[v]&#xff0c;在同一个棵子…

在 alpine 中使用 NPOI

在 alpine 中使用 NPOIIntro在 .net 中常使用 NPOI 来做 Excel 的导入导出&#xff0c;NPOI 从 2.4.0 版本开始支持 .netstandard2.0&#xff0c;在 dotnet core 应用也可以用 DotNetCore.NPOI。对于 .NET Core 应用来说&#xff0c;如果没有特殊的需求&#xff0c;alpine 是最…

Educational Codeforces Round 16 C. Magic Odd Square 矩阵构造

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给你一个奇数nnn&#xff0c;让你构造一个n∗nn*nn∗n的矩阵&#xff0c;矩阵的每个位置依次填上[1,n∗n]之内的数[1,n*n]之内的数[1,n∗n]之内的数&#xff0c;满足每行、每列、以及主对角线的和都是奇数。…

关于WCF、WebAPI、WebService之间的区别总结 分布式通信技术

早在1996年Gartner就前瞻性地提出了面向服务架构的思想(SOA)&#xff0c;SOA 的走红在很大程度上归功于 Web Service 标准的成熟和应用的普及。Service Oriented Ambiguity 中文一般理解为&#xff1a;面向服务架构&#xff0c;简称SOA&#xff0c;这个概念算得上微服务的鼻祖了…

.NET中的值类型与引用类型

.NET中的值类型与引用类型这是一个常见面试题&#xff0c;值类型(Value Type)和引用类型(Reference Type)有什么区别&#xff1f;他们性能方面有什么区别&#xff1f;TL;DR&#xff08;先看结论&#xff09;值类型引用类型创建位置栈托管堆赋值时复制值复制引用动态内存分配无需…

跨语言调用Hangfire定时作业服务

背景Hangfire允许您以非常简单但可靠的方式执行后台定时任务的工作。内置对任务的可视化操作。非常方便。但令人遗憾的是普遍都是业务代码和hagnfire服务本身聚合在一个程序中运行&#xff0c;极大的限制了hangfire的扩展和跨语言调用。所以萌生了开发一个支持restful api调用的…

可落地微服务on k8s的持续集成/部署方案

我们隔一流的软件生产工艺还有多远&#xff1f;在距离15000公里外&#xff0c;Amazon一年可以进行5000万次部署&#xff0c;在这一边某电商平台的研发部门里&#xff0c;让他们引以为傲的是他们正在进行“敏捷”开发模式&#xff0c;并对外号称他们是以每周为迭代来进行升级。时…

Codeforces Round #579 (Div. 3) F1. Complete the Projects (easy version) 排序 + 贪心

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 思路&#xff1a; 比较直观的想法就是对于bi≥0b_i\ge0bi​≥0的项目&#xff0c;我们将aia_iai​从小到大排序&#xff0c;让后依次加bib_ibi​&#xff0c;如果有取不到的&#xff0c;显然就无解。否则再看…

历久弥新 - 微软万亿市值背后的文化支撑(下)|DevOps案例研究

内容来源&#xff1a;DevOps案例深度研究-Microsoft文化支撑研究战队&#xff08;本文只展示部分PPT研究成果&#xff0c;更多细节请关注案例分享会&#xff0c;及本公众号。&#xff09;本案例内容贡献者&#xff1a;陈飞&#xff08;Topic Leader&#xff09;、陈雨卿、郭子奇…

架构杂谈《九》

微服务与轻量级通信机制微服务架构是一种架构模式&#xff0c;它提倡将单一应用程序划分成一组小的服务&#xff0c;服务之间胡亮协调、互相配合&#xff0c;为用户提供最终价值。在微服务架构中&#xff0c;服务与服务之间通信时&#xff0c;通常是通过轻量级的通信机制&#…

Codeforces Round #585 (Div. 2) E. Marbles 状压dp + 逆序对

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 思路&#xff1a; 考虑数列最终的状态一定是相同颜色在一起&#xff0c;所以我们发现他的颜色是有顺序的&#xff01;显然可以用状压dpdpdp来枚举颜色的顺序&#xff0c;但是又有问题了&#xff0c;你怎么确…

GitLab CI 自动部署netcore web api 到Docker

前端篇文章中&#xff0c;我们已经成功的将asp.net core webapi在Docker容器中运行&#xff0c;并且部署了一套自己的GitLab环境。.Net & Docker&#xff08;二&#xff09;5分钟快速用Docker部署你自己的GitLab.Net & Docker&#xff08;一&#xff09;在Docker容器上…