线段树&数链剖分
傻逼线段树,傻逼数剖
线段树
定义:
线段树的思想和分治思想很相像。
线段树的每一个节点都储存着一段区间[L…R]的信息,其中叶子节点L=R。它的大致思想是:将一段大区间平均地划分成2个小区间,每一个小区间都再平均分成2个更小区间……以此类推,直到每一个区间的L等于R(这样这个区间仅包含一个节点的信息,无法被划分)。通过对这些区间进行修改、查询,来实现对大区间的修改、查询。
这样一来,每一次修改、查询的时间复杂度都只为O(log2n)。
void build(int l,int r,int i){//建树,为当前左边界,r为右边界,i为编号
tree[i].l=l;//更新边界值
tree[i].r=r;
if(l==r){//如果是最底层的节点,sum就是本身
tree[i].sum=input[l];
return ;
}
int mid=(l+r)/2;
build(l,mid,2*i);//左边部分建树
build(mid+1,r,2*i+1);//右边部分建树
tree[i].sum=(tree[2*i].sum+tree[2*i+1].sum)%mod;//递归返回时更新sum值
}
区间修改:
void add(int i,int L,int R,int k){//区间修改 ;
if(tree[i].l>=L&&tree[i].r<=R){//被完全包含
tree[i].sum=tree[i].sum+(tree[i].r-tree[i].l+1)*k; //修改区间
tree[i].lazy+=k;//更新延迟标记
return ;
}
push_down(i);//没有完全包含的话就先下传懒标记
int mid=(tree[i].l+tree[i].r)/2;
if(L<=mid) add(2*i,L,R,k);//左边有重合走左边
if(mid<R) add(2*i+1,L,R,k);//右边有重合走右边
//这里一定不能写mid<=R,不然会死循环
tree[i].sum=tree[2*i].sum+tree[2*i+1].sum//更新sum值
}
区间查询:
int ask(int i,int L,int R){//区间查询
if(tree[i].l>=L&&tree[i].r<=R) return tree[i].sum;//完全包含,道理同区间修改
push_down(i);//没有完全包含就下传延迟标记
int ans=0;
int mid=(tree[i].l+tree[i].r)/2;
if(mid>=L) ans=(ans+ask(2*i,L,R));
if(mid<R) ans=(ans+ask(2*i+1,L,R));//记录答案
return ans;
}
下传延迟标记(push_down操作):
void push_down(int i){//延迟标记下移
if(tree[i].lazy){
tree[2*i].sum=(tree[2*i].sum+(tree[2*i].r-tree[2*i].l+1)* tree[i].lazy%mod)%mod;
tree[2*i+1].sum=(tree[2*i+1].sum+(tree[2*i+1].r-tree[2*i+1].l+1)* tree[i].lazy%mod)%mod;
tree[2*i].lazy+=tree[i].lazy;
tree[2*i+1].lazy+=tree[i].lazy;
tree[i].lazy=0;
}
}
延迟标记的作用:
有些时候修改了也不一定会去查询,于是就打上延迟标记,需要的时候再下传。
板题:https://www.luogu.com.cn/problem/P3372
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
int type,x,y,k;
long long input[N];
struct node {
int l;
int r;
long long sum;
long long add;
} tree[N*4]; void spread(int i) {
if(tree[i].add) {
tree[2*i].sum+=tree[i].add*(tree[2*i].r-tree[2*i].l+1);
tree[2*i+1].sum+=tree[i].add*(tree[2*i+1].r-tree[2*i+1].l+1);
tree[2*i].add+=tree[i].add;
tree[2*i+1].add+=tree[i].add;
tree[i].add=0;
}
} void build(int i,int l,int r) {
tree[i].l=l;
tree[i].r=r;
if(l==r) {
tree[i].sum=input[l];
return ;
}
int mid=(l+r)/2;
build(2*i,l,mid);
build(2*i+1,mid+1,r);
tree[i].sum=tree[2*i].sum+tree[2*i+1].sum;
} void change(int i,int l,int r,int k) {
if(l<=tree[i].l&&r>=tree[i].r) {
tree[i].sum+=k*(tree[i].r-tree[i].l+1);
tree[i].add+=k;
return ;
}
spread(i);
int mid=(tree[i].l+tree[i].r)/2;
if(l<=mid)change(2*i,l,r,k);
if(r>mid)change(2*i+1,l,r,k);
tree[i].sum=tree[2*i].sum+tree[2*i+1].sum;
} long long check(int i,int l,int r) {
if(l<=tree[i].l&&r>=tree[i].r) {
return tree[i].sum;
}
spread(i);
long long flag=0;
int mid=(tree[i].l+tree[i].r)/2;
if(l<=mid) flag+=check(2*i,l,r);
if(r>mid) flag+=check(2*i+1,l,r);
return flag;
} int main() {
cin>>n>>m;
for(int i=1; i<=n; i++) {
cin>>input[i];
}
build(1,1,n);
for(int i=1; i<=m; i++) {
cin>>type;
if(type==1) {
cin>>x>>y>>k;
change(1,x,y,k);
}
if(type==2) {
cin>>x>>y;
cout<<check(1,x,y)<<endl;
}
}
return 0;
}
好的接下来来到——
树剖
定义:我们以某种规则将一棵树剖分成若干条竖直方向上的链,每次维护时可以一次跳一条链、并借助一些强大的线性数据结构来维护(通常链的数量很少),这样就大大优化了时间复杂度,足以解决很多线性结构搬到树上的题目。
变量:
//son[N] 重儿子的编号,若没有重儿子则编号为-1
//size[N] 子树的大小
//f[N] 父亲节点的编号
//d[N] 结点的深度
//top[N] 所在链的链端
//id[N] 经过重链剖分后的新编号
//rk[N] 有rk[id[i]]=i
将树剖分成链的过程中,我们一共要进行两次dfs:
void dfs1(int x,int fa,int depth){//x:当前结点 fa:父结点 depth:当前结点深度
f[x]=fa;//更新父结点
d[x]=depth;//更新深度
size[x]=1;//子树大小初始化:根节点本身
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(y==fa) continue ;
dfs1(y,x,depth+1);
if(size[y]>size[son[x]]) son[x]=y;//更新重儿子
size[x]+=size[y];//更新子树大小
}
return ;
} //dfs1更新f[N],d[N],size[N],son[N]
void dfs2(int u,int t){//u为当前结点,t为链段
top[u]=t;
id[u]=++cnt;
rk[cnt]=u;//新的编号
if(!son[u]) return ;
dfs2(son[u],t);//优先遍历重儿子,使一条链上编号连续
for(int i=head[u];i;i=nex[i]){
int y=ver[i];
if(y==son[u]||y==f[u]) continue;
dfs2(y,y);//再建一条链
}
}//dfs2更新top[N],id[N],rk[N]
至此我们剖分的过程就已经完成了,现在让我们看看在题目中树链剖分有什么用:
题目:https://www.luogu.com.cn/problem/P3384
题目要求我们进行如下操作:
操作 1: 格式: 1 x y z 表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z。(区间修改)
操作 2: 格式: 2 x y 表示求树从 x 到 y 结点最短路径上所有节点的值之和。(区间查询)
操作 3: 格式: 3 x z 表示将以 x 为根节点的子树内所有节点值都加上 z。(区间修改)
操作 4: 格式: 4 x 表示求以 x 为根节点的子树内所有节点值之和。(区间查询)
操作一:
void func1(int x,int y,int k){//将树从 x到 y结点最短路径上所有节点的值都加上k
if(d[x]<d[y]) swap(x,y);
while(top[x]!=top[y]){//循环,直到这两个点处于同一条链
if(d[top[x]]<d[top[y]]) swap(x,y);//规范
add(1,id[top[x]],id[x],k);
x=f[top[x]];
}
if(d[x]>d[y]) swap(x,y);//深度较浅的一定是序号较小的
add(1,id[x],id[y],k);
}
其实原理就和倍增求LCA差不多。
操作二:
void func2(int x,int y){
int ans=0;
while(top[x]!=top[y]){
if(d[top[x]]<d[top[y]]) swap(x,y);
ans=(ans+ask(1,id[top[x]],id[x]))%mod;
x=f[top[x]];
}
if(d[x]>d[y]) swap(x,y);//道理同上
ans=(ans+ask(1,id[x],id[y]));
cout<<ans%mod<<endl;
}
原理和func1差不多啦~
操作三&&操作四:
这里的处理比较巧妙。
在树剖中,一条链的编号是连续的,因此一棵子树的编号也是连续的。
所以直接用线段树的区间修改和区间查询操作就行了。
Code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,m,root,mod;
int idx=0;
struct node{
int l;
int r;
int sum;
int lazy;
}tree[4*N];
int head[N],ver[2*N],nex[2*N],son[N],size[N],f[N],d[N],top[N],id[N],rk[N];
int input[N];
int cnt=0;
void add_e(int x,int y){
ver[++idx]=y;
nex[idx]=head[x];
head[x]=idx;
} void build(int l,int r,int i){//常规建树
tree[i].l=l;
tree[i].r=r;
if(l==r){
tree[i].sum=input[rk[l]]%mod;
return ;
}
int mid=(l+r)/2;
build(l,mid,2*i);
build(mid+1,r,2*i+1);
tree[i].sum=(tree[2*i].sum+tree[2*i+1].sum)%mod;
} void push_down(int i){//延迟标记下移
if(tree[i].lazy){
tree[2*i].sum=(tree[2*i].sum+(tree[2*i].r-tree[2*i].l+1)* tree[i].lazy%mod)%mod;
tree[2*i+1].sum=(tree[2*i+1].sum+(tree[2*i+1].r-tree[2*i+1].l+1)* tree[i].lazy%mod)%mod;
tree[2*i].lazy+=tree[i].lazy;
tree[2*i+1].lazy+=tree[i].lazy;
tree[i].lazy=0;
}
} int ask(int i,int L,int R){//区间查询
if(tree[i].l>=L&&tree[i].r<=R) return tree[i].sum;
push_down(i);
int ans=0;
int mid=(tree[i].l+tree[i].r)/2;
if(mid>=L) ans=(ans+ask(2*i,L,R))%mod;
if(mid<R) ans=(ans+ask(2*i+1,L,R))%mod;
return ans%mod;
} void add(int i,int L,int R,int k){//区间修改 ;
if(tree[i].l>=L&&tree[i].r<=R){
tree[i].sum=(tree[i].sum+(tree[i].r-tree[i].l+1)*k%mod)%mod;
tree[i].lazy+=k;
return ;
}
push_down(i);
int mid=(tree[i].l+tree[i].r)/2;
if(L<=mid) add(2*i,L,R,k);
if(mid<R) add(2*i+1,L,R,k);
tree[i].sum=(tree[2*i].sum+tree[2*i+1].sum)%mod;
} void dfs1(int x,int fa,int depth){//x:当前结点 fa:父结点 depth:当前结点深度
f[x]=fa;//更新父结点
d[x]=depth;//更新深度
size[x]=1;//子树大小初始化:根节点本身
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(y==fa) continue ;
dfs1(y,x,depth+1);
if(size[y]>size[son[x]]) son[x]=y;//更新重儿子
size[x]+=size[y];//更新子树大小
}
return ;
} void dfs2(int u,int t){//u为当前结点,t为链段
top[u]=t;
id[u]=++cnt;
rk[cnt]=u;//新的编号
if(!son[u]) return ;
dfs2(son[u],t);//优先遍历重儿子,使一条链上编号连续
for(int i=head[u];i;i=nex[i]){
int y=ver[i];
if(y==son[u]||y==f[u]) continue;
dfs2(y,y);//再建一条链
}
} void func1(int x,int y,int k){//将树从 x到 y结点最短路径上所有节点的值都加上k
if(d[x]<d[y]) swap(x,y);
while(top[x]!=top[y]){//循环,直到这两个点处于同一条链
if(d[top[x]]<d[top[y]]) swap(x,y);//规范
add(1,id[top[x]],id[x],k);
x=f[top[x]];
}
if(d[x]>d[y]) swap(x,y);//深度较浅的一定是序号较小的
add(1,id[x],id[y],k);
} void func2(int x,int y){
int ans=0;
while(top[x]!=top[y]){
if(d[top[x]]<d[top[y]]) swap(x,y);
ans=(ans+ask(1,id[top[x]],id[x]))%mod;
x=f[top[x]];
}
if(d[x]>d[y]) swap(x,y);//道理同上
ans=(ans+ask(1,id[x],id[y]));
cout<<ans%mod<<endl;
}
signed main(){
cin>>n>>m>>root>>mod;
for(int i=1;i<=n;i++){
cin>>input[i];//输入节点初始值
}
for(int i=1;i<=n-1;i++){
int x,y;
cin>>x>>y;
add_e(x,y);
add_e(y,x);//建图
}
dfs1(root,0,1);//第一次dfs求son,depth,f,size
dfs2(root,root);//第二次dfs求id,rk,将树拆成链表
build(1,n,1);//建树
for(int i=1;i<=m;i++){
int type;
cin>>type;
if(type==1){
int x,y,z;
cin>>x>>y>>z;
func1(x,y,z);
}
if(type==2){
int x,y;
cin>>x>>y;
func2(x,y);
}
if(type==3){
int x,z;
cin>>x>>z;
add(1,id[x],id[x]+size[x]-1,z);
}
if(type==4){
int x;
cin>>x;
cout<<ask(1,id[x],id[x]+size[x]-1)%mod<<endl;
}
}
return 0;
}
完结撒花*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。
线段树&数链剖分的更多相关文章
- G - Game HDU - 5242 (数链剖分)
题目链接: G - Game HDU - 5242 题目大意:首先是T组测试样例,给出一颗以1节点为根的树,每个节点有各自的价值,有m次从根节点出发向下走到叶子节点的机会,每次会得到所有经过节点的权值 ...
- 数链剖分(树的统计Count )
题目链接:https://cn.vjudge.net/contest/279350#problem/C 具体思路:单点更新,区间查询,查询的时候有两种操作,查询区间最大值和区间和. 注意点:在查询的时 ...
- 数链剖分(Aragorn's Story )
题目链接:https://vjudge.net/contest/279350#problem/A 题目大意:n个点,m条边,然后q次询问,因为在树上,两个点能确定一条直线,我们可以对这条直线上的所有值 ...
- POJ 2352 Stars 线段树 数星星
转载自 http://www.cnblogs.com/fenshen371/archive/2013/07/25/3214927.html 题意:已知n个星星的坐标.每个星星都有一个等级,数值等于坐标 ...
- 数链剖分(Tree)
题目链接:https://cn.vjudge.net/contest/279350#problem/D 题目大意:操作,单点查询,区间取反,询问区间最大值. AC代码: #include<ios ...
- 数链剖分(Housewife Wind )
题目链接:https://vjudge.net/contest/279350#problem/B 题目大意:给你n,q,s.n指的是有n个点,q代表有q次询问,s代表的是起点.然后接下来会有n-1条 ...
- UOJ#30/Codeforces 487E Tourists 点双连通分量,Tarjan,圆方树,树链剖分,线段树
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ30.html 题目传送门 - UOJ#30 题意 uoj写的很简洁.清晰,这里就不抄一遍了. 题解 首先建 ...
- POJ 2763 Housewife Wind 【树链剖分】+【线段树】
<题目链接> 题目大意: 给定一棵无向树,这棵树的有边权,这棵树的边的序号完全由输入边的序号决定.给你一个人的起点,进行两次操作: 一:该人从起点走到指定点,问你这段路径的边权总和是多少. ...
- BZOJ1758[Wc2010]重建计划——分数规划+长链剖分+线段树+二分答案+树形DP
题目描述 输入 第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案,每行三个正整数Ai, ...
随机推荐
- Centos 搭建Hadoop
Centos搭建Hadoop 一.搭建Hadoop需要JDK环境,首先配置JDK 二.下载haoop 三.在Centos服务器上解压下载好的安装包 四.修改配置文件 4.1 hadoop-env.sh ...
- 数组赋值到select
function _oneClassData() { var options = []; $(oneClass).each(function (i, item) { var option = {}; ...
- leetcode常见问题
开学了 开始每日刷leetcode了 ,开一个新分类记录做题过程和心得. 1.出现本地调试无问题但提交后报错时,很有可能是全局变量导致的,解决办法 (1).尽量写成局部变量,函数尽量传参进入. (2 ...
- Codeforces Round #652 (Div. 2) E. DeadLee(贪心)
题目链接:https://codeforces.com/contest/1369/problem/E 题意 Lee 有 $n$ 种不同种类的食物和 $m$ 个朋友,每种食物有 $w_i$ 个,每个朋友 ...
- AC自动机——看似KMP在跑,其实fail在跳
先存代码 AC自动机(简单版) #include<bits/stdc++.h> #define maxn 1000007 using namespace std; int n,ans; i ...
- Long Long Message POJ - 2774 后缀数组
The little cat is majoring in physics in the capital of Byterland. A piece of sad news comes to him ...
- C# 网络流
流(stream)是对串行传输的数据的一种抽象表示,底层的设备可以是文件.外部设备.主存.网络套接字等等. 流有三种基本的操作:写入.读取和查找. 如果数据从内存缓冲区传输到外部源,这样的流叫作&qu ...
- Python_变量作用域与修改
引用全局变量,不需要golbal声明,修改全局变量,需要使用global声明,特别地,列表.字典等如果只是修改其中元素的值(而不是整体赋值的形式),可以直接使用全局变量,不需要global声明. 参考 ...
- 2018牛客多校第一场 E-Removal【dp】
题目链接:戳这里 转自:戳这里 题意:长度为n的序列,删掉m个数字后有多少种不同的序列.n<=10^5,m<=10. 题解:dp[i][j]表示加入第i个数字后,总共删掉j个数字时,有多少 ...
- Android四大组件简介:Android 基础知识,开发教程
Android 四大组件: Activity.Service.Broadcast Receiver.Content Provider. http://developer.android.com/int ...