数据结构——线段树(SegmentTree)

线段树

引入

例题 1

给定一个数组 a a a 求数组中下标为 l − r l - r lr元素的和

看到这题大家都很容易想到用前缀和以 O ( n ) O(n) O(n)预处理, O ( 1 ) O(1) O(1)求解

例题 2

给定一个数组 a a a ,操作次数 ,及操作符 o p t opt opt
o p t = 1 opt=1 opt=1时 求数组中下标为 l − r l - r lr元素的和,
o p t = 2 opt=2 opt=2时 将数组中下标为 l − r l - r lr元素的+ d d d

很明显如果我们用前缀和优化,虽然查询很快,但每次修改都是 O ( r − l + 1 ) O(r-l+1) O(rl+1),当操作次数多时,仍会超时

么有没有一种查询快且修改快的东西呢?
那就是——线段树

线段树概念

“线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。”(选自百度)

有了线段树,我们就可以在 O ( l o g n ) O(log n) O(logn)的时间内进行修改和查询

模板

现附上大家最爱的模板

struct SegmentTree{
	int l,r,size;
	ll sum,tag;
	#define ls(x) (x<<1)
	#define rs(x) (x<<1|1)
}tr[MAX*4];

ll a[MAX];

inline void Push_up(int rt){
	tr[rt].sum = tr[ls(rt)].sum+tr[rs(rt)].sum;
}

void Build(int l,int r,int rt){
	tr[rt].l = l; tr[rt].r = r; tr[rt].size = r-l+1;
	if(l == r){
		tr[rt].sum = a[l];
		return;
	}
	int mid = (l+r)>>1;
	Build(l,mid,ls(rt));
	Build(mid+1,r,rs(rt));
	Push_up(rt);
}

inline void Push_down(int rt){
	if(!tr[rt].tag) return;
	tr[ls(rt)].tag += tr[rt].tag;
	tr[rs(rt)].tag += tr[rt].tag;
	tr[ls(rt)].sum += tr[rt].tag*tr[ls(rt)].size;
	tr[rs(rt)].sum += tr[rt].tag*tr[rs(rt)].size;
	tr[rt].tag = 0;
}

void Update(int rt,int l,int r,ll c){
	if(tr[rt].l >= l && tr[rt].r <= r){
		tr[rt].sum += tr[rt].size*c;
		tr[rt].tag += c;
		return;
	}
	Push_down(rt);
	int mid = (tr[rt].l+tr[rt].r)>>1;
	if(mid >= l) Update(ls(rt),l,r,c);
	if(mid <  r) Update(rs(rt),l,r,c);
	Push_up(rt);
}

ll Query(int rt,int l,int r){
	if(tr[rt].l >= l && tr[rt].r <= r) return tr[rt].sum;
	Push_down(rt);
	int mid = (tr[rt].l+tr[rt].r)>>1;
	ll cnt = 0;
	if(mid >= l) cnt += Query(ls(rt),l,r);
	if(mid <  r) cnt += Query(rs(rt),l,r);
	return cnt;
}

线段树构造

线段树
图片来源于网络

线段树的构造如其概念所示,采用二分思想,每个节点贮存范围与范围权值和,其左右子树范围分别为父节点的范围的左半段与右半段,直到范围内只剩一个元素

由于它是完全二叉树,所以我们可以用下标的2倍储存其左节点,二倍加一储存右节点

代码实现

注: r t < < 1 = r t ∗ 2 , , r t < < 1 ∣ 1 = r t ∗ 2 + 1 rt<<1 = rt * 2,,rt<<1|1 = rt * 2+1 rt<<1=rt2,rt<<1∣1=rt2+1

inline void Push_up(int rt){//将左右子树求和
	sum[rt] = sum[rt<<1]+sum[rt<<1|1];
}
void Build(int l,int r,int rt){//l表示左边界,rb表示右边界,rt表示目前位置 
	if(l == r){
		sum[rt] = a[l];
		return;
	}
	int mid = (l+r)>>1;
	Build(l,mid,rt<<1);//左子树
	Build(mid+1,r,rt<<1|1);//右子数
	Push_up(rt);//求和
}

线段树区间查询

线段树区间查询
图片来源于网络

如图所示,线段树的查询就是不断向下找,直到节点范围在查询范围内即可

代码实现
long long Query(int nl,int nr,int l,int r,int rt){
//nl,nr为要查询的左右边界,l,r为目前查询到的左右边界.rt为目前位置
	if(l >= nl && r <= nr) return sum[rt];//如果整个在查询范围内,就不用查下去了
	int mid = (l+r)>>1;
	int ans = 0;//存和
	if(mid >= nl) ans += Query(nl,nr,l,mid,rt<<1);//查左子树
	if(mid < nr) ans += Query(nl,nr,mid+1,r,rt<<1|1);//查右子树
	return ans;
}

区间修改&懒标记

懒标记是线段树的核心

修改区间[l,r],[l,r]需要进行打懒标记的操作来减少时间消耗。
毕竟如果你不查询到这个节点这个节点也没必要一直改呀

设一个数组 tag , tag[i] 表示编号为 i 的节点的懒标记。

代码实现(以区间求和为例)
inline void Push_down(long long rt,long long l,long long r){
	if(!tag[rt]) return;
	long long mid = (l+r)>>1;
	tag[rt<<1] += tag[rt];
	tag[rt<<1|1] += tag[rt];//将懒标记传到儿子树
	sum[rt<<1] += (mid-l+1)*tag[rt];
	sum[rt<<1|1] += (r-mid)*tag[rt];//懒标记对儿子树进行修改
	tag[rt] = 0;
}
void Update(long long nl,long long nr,long long c,long long l,long long r,long long rt){
//c表示要加的数,其余同上
	if(l >= nl && r <= nr){
		sum[rt] += c*(r-l+1);
		tag[rt] += c;
		return;
	}
	int mid = (l+r)>>1;
	Push_down(rt,l,r);
	if(mid >= nl) Update(nl,nr,c,l,mid,rt<<1);
	if(mid < nr) Update(nl,nr,c,mid+1,r,rt<<1|1);
	Push_up(rt);//修改完再求和
}

至此,线段树基本就讲完了,是不是非常简单
接下来就是一些模板与练习

单点增加,区间求和

模板题

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  • 将某一个数加上 x x x

  • 求出某区间每一个数的和

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX = 5e5+10;
long long read(){
	long long f=1,r=0;
	char ch;
	do ch=getchar(); while(!isdigit(ch) && ch!='-');
	if(ch=='-') f=-1,ch=getchar();
	do r=r*10+ch-48,ch=getchar(); while(isdigit(ch));
	return r*f;
}
void write(long long X){
	if(X < 0) putchar('-'),X=-X;
if(X>9) write(X/10); 
putchar(X%10+'0'); 
}
long long n,m;
long long a[MAX],sum[MAX<<5];
inline void Push_up(int rt){sum[rt] = sum[rt<<1]+sum[rt<<1|1];}
void Build(int l,int r,int rt){
	if(l == r){
		sum[rt] = a[l];
		return;
	}
	int mid = (l+r)>>1;
	Build(l,mid,rt<<1);
	Build(mid+1,r,rt<<1|1);
	Push_up(rt);
}
void Update(int nl,int c,int l,int r,int rt){
	if(l == r){
		sum[rt]+=c;
		return;
	}
	int mid = (l+r)>>1;
	if(mid >= nl) Update(nl,c,l,mid,rt<<1);
	else Update(nl,c,mid+1,r,rt<<1|1);
	Push_up(rt);
}
long long Query(int nl,int nr,int l,int r,int rt){
	if(l >= nl && r <= nr) return sum[rt];
	int mid = (l+r)>>1;
	int ans = 0;
	if(mid >= nl) ans += Query(nl,nr,l,mid,rt<<1);
	if(mid < nr) ans += Query(nl,nr,mid+1,r,rt<<1|1);
	return ans;
}
int main(){
	n = read(); m = read();
	for(int i=1;i<=n;i++)
		a[i] = read();
	Build(1,n,1);
	while(m--){
		int op = read();
		if(op == 1){
			int x = read(),k = read();
			Update(x,k,1,n,1);
		}
		else{
			int x = read(),y = read();
			write(Query(x,y,1,n,1));
			puts("");
		}
	}
	return 0;
}

区间增加,单点查询

模板题

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 x x x

  2. 求出某一个数的值。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX = 5e5+10;
long long read(){
	long long f=1,r=0;
	char ch;
	do ch=getchar(); while(!isdigit(ch) && ch!='-');
	if(ch=='-') f=-1,ch=getchar();
	do r=r*10+ch-48,ch=getchar(); while(isdigit(ch));
	return r*f;
}
void write(long long X){
	if(X < 0) putchar('-'),X=-X;
	if(X>9) write(X/10); 
	putchar(X%10+'0'); 
}
long long n,m;
long long a[MAX],sum[MAX<<5],tag[MAX<<5];
inline void Push_up(int rt){sum[rt] = sum[rt<<1]+sum[rt<<1|1];}
void Build(long long l,long long r,long long rt){
	if(l == r){
		sum[rt] = a[l];
		return;
	}
	long long mid = (l+r)>>1;
	Build(l,mid,rt<<1);
	Build(mid+1,r,rt<<1|1);
	Push_up(rt);
}
inline void Push_down(long long rt,long long l,long long r){
	//if(!tag[rt]) return;
	long long mid = (l+r)>>1;
	tag[rt<<1] += tag[rt];
	tag[rt<<1|1] += tag[rt];
	sum[rt<<1] += (mid-l+1)*tag[rt];
	sum[rt<<1|1] += (r-mid)*tag[rt];
	tag[rt] = 0;
}
void Update(long long nl,long long nr,long long c,long long l,long long r,long long rt){
	if(l >= nl && r <= nr){
		sum[rt] += c*(r-l+1);
		tag[rt] += c;
		return;
	}
	int mid = (l+r)>>1;
	Push_down(rt,l,r);
	if(mid >= nl) Update(nl,nr,c,l,mid,rt<<1);
	if(mid < nr) Update(nl,nr,c,mid+1,r,rt<<1|1);
	Push_up(rt);
}
long long Query(long long nl,long long l,long long r,long long rt){
	if(l == r) return sum[rt];
	Push_down(rt,l,r);
	long long mid = (l+r)>>1;
	if(mid >= nl) return Query(nl,l,mid,rt<<1);
	else return Query(nl,mid+1,r,rt<<1|1);
}
int main(){
	n = read(); m = read();
	for(int i=1;i<=n;i++)
		a[i] = read();
	Build(1,n,1);
	while(m--){
		int op = read();
		if(op == 1){
			long long x = read(),y = read(),k = read();
			Update(x,y,k,1,n,1);
		}
		else{
			long long x = read();
			write(Query(x,1,n,1));
			puts("");
		}
	}
	return 0;
}

区间增加,区间求和

模板题

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 k k k
  2. 求出某区间每一个数的和。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX = 1e5+10;
long long read(){
	long long f=1,r=0;
	char ch;
	do ch=getchar(); while(!isdigit(ch) && ch!='-');
	if(ch=='-') f=-1,ch=getchar();
	do r=r*10+ch-48,ch=getchar(); while(isdigit(ch));
	return r*f;
}
void write(long long X){
	if(X < 0) putchar('-'),X=-X;
	if(X>9) write(X/10); 
	putchar(X%10+'0'); 
}
long long n,m;
long long a[MAX],sum[MAX<<5],tag[MAX<<5];
inline void Push_up(int rt){sum[rt] = sum[rt<<1]+sum[rt<<1|1];}
void Build(long long l,long long r,long long rt){
	if(l == r){
		sum[rt] = a[l];
		return;
	}
	long long mid = (l+r)>>1;
	Build(l,mid,rt<<1);
	Build(mid+1,r,rt<<1|1);
	Push_up(rt);
}
inline void Push_down(long long rt,long long l,long long r){
	//if(!tag[rt]) return;
	long long mid = (l+r)>>1;
	tag[rt<<1] += tag[rt];
	tag[rt<<1|1] += tag[rt];
	sum[rt<<1] += (mid-l+1)*tag[rt];
	sum[rt<<1|1] += (r-mid)*tag[rt];
	tag[rt] = 0;
}
void Update(long long nl,long long nr,long long c,long long l,long long r,long long rt){
	if(l >= nl && r <= nr){
		sum[rt] += c*(r-l+1);
		tag[rt] += c;
		return;
	}
	int mid = (l+r)>>1;
	Push_down(rt,l,r);
	if(mid >= nl) Update(nl,nr,c,l,mid,rt<<1);
	if(mid < nr) Update(nl,nr,c,mid+1,r,rt<<1|1);
	Push_up(rt);
}
long long Query(long long nl,long long nr,long long l,long long r,long long rt){
	if(l >= nl && r <= nr) return sum[rt];
	Push_down(rt,l,r);
	long long mid = (l+r)>>1;
	long long ans = 0;
	if(mid >= nl) ans += Query(nl,nr,l,mid,rt<<1);
	if(mid < nr) ans += Query(nl,nr,mid+1,r,rt<<1|1);
	return ans;
}
int main(){
	n = read(); m = read();
	for(int i=1;i<=n;i++)
		a[i] = read();
	Build(1,n,1);
	while(m--){
		int op = read();
		if(op == 1){
			long long x = read(),y = read(),k = read();
			Update(x,y,k,1,n,1);
		}
		else{
			long long x = read(),y = read();
			write(Query(x,y,1,n,1));
			puts("");
		}
	}
	return 0;
}

区间加法&区间乘法

模板题

题目描述

如题,已知一个数列,你需要进行下面三种操作:

  • 将某区间每一个数乘上 x x x

  • 将某区间每一个数加上 x x x

  • 求出某区间每一个数的和

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX = 1e5+10;
long long read(){
	long long f=1,r=0;
	char ch;
	do ch=getchar(); while(!isdigit(ch) && ch!='-');
	if(ch=='-') f=-1,ch=getchar();
	do r=r*10+ch-48,ch=getchar(); while(isdigit(ch));
	return r*f;
}
void write(long long X){
	if(X < 0) putchar('-'),X=-X;
if(X>9) write(X/10); 
putchar(X%10+'0'); 
}
long long n,m,p;
long long a[MAX],sum[MAX<<5],tag1[MAX<<5],tag2[MAX<<5];
inline void Push_up(int rt){sum[rt] = sum[rt<<1]+sum[rt<<1|1];}
void Build(long long l,long long r,long long rt){
	if(l == r){
		sum[rt] = a[l];
		return;
	}
	tag2[rt] = 1;
	long long mid = (l+r)>>1;
	Build(l,mid,rt<<1);
	Build(mid+1,r,rt<<1|1);
	Push_up(rt);
}
inline void Push_down(long long rt,long long l,long long r){
	long long mid = (l+r)>>1;
	tag2[rt<<1] = tag2[rt]*tag2[rt<<1]%p;
	tag2[rt<<1|1] = tag2[rt]*tag2[rt<<1|1]%p;
	tag1[rt<<1] = (tag1[rt<<1]*tag2[rt]+tag1[rt])%p;
	tag1[rt<<1|1] = (tag1[rt<<1|1]*tag2[rt]+tag1[rt])%p;
	sum[rt<<1] = (sum[rt<<1]*tag2[rt]+tag1[rt]*(mid-l+1))%p;
	sum[rt<<1|1] = (sum[rt<<1|1]*tag2[rt]+tag1[rt]*(r-mid))%p;
	tag1[rt] = 0;
	tag2[rt] = 1;
}
void Update1(long long nl,long long nr,long long c,long long l,long long r,long long rt){
	if(l >= nl && r <= nr){
		sum[rt] += c*(r-l+1);
		sum[rt] %= p;
		tag1[rt] += c;
		return;
	}
	int mid = (l+r)>>1;
	Push_down(rt,l,r);
	if(mid >= nl) Update1(nl,nr,c,l,mid,rt<<1);
	if(mid < nr) Update1(nl,nr,c,mid+1,r,rt<<1|1);
	Push_up(rt);
}
void Update2(long long nl,long long nr,long long c,long long l,long long r,long long rt){
	if(l >= nl && r <= nr){
		sum[rt] *= c; sum[rt] %= p;
		tag2[rt] *= c;
		tag1[rt] *= c;
		return;
	}
	int mid = (l+r)>>1;
	Push_down(rt,l,r);
	if(mid >= nl) Update2(nl,nr,c,l,mid,rt<<1);
	if(mid < nr) Update2(nl,nr,c,mid+1,r,rt<<1|1);
	Push_up(rt);
}
long long Query(long long nl,long long nr,long long l,long long r,long long rt){
	if(l >= nl && r <= nr) return sum[rt];
	Push_down(rt,l,r);
	long long mid = (l+r)>>1;
	long long ans = 0;
	if(mid >= nl) ans += Query(nl,nr,l,mid,rt<<1);
	if(mid < nr) ans += Query(nl,nr,mid+1,r,rt<<1|1);
	return ans%p;
}
int main(){
	n = read(); m = read(); p = read();
	for(int i=1;i<=n;i++)
		a[i] = read();
	Build(1,n,1);
	while(m--){
		int op = read();
		if(op == 1){
			long long x = read(),y = read(),k = read();
			Update2(x,y,k,1,n,1);
		}
		else if(op == 2){
			long long x = read(),y = read(),k = read();
			Update1(x,y,k,1,n,1);
		}
		else{
			long long x = read(),y = read();
			write(Query(x,y,1,n,1));
			puts("");
		}
	}
	return 0;

}

练习题

XOR的艺术

题目描述

AKN 觉得第一题太水了,不屑于写第一题,所以他又玩起了新的游戏。在游戏中,他发现,这个游戏的伤害计算有一个规律,规律如下

  1. 拥有一个伤害串,是一个长度为 n n n 的只含字符 0 和字符 1 的字符串。规定这个字符串的首字符是第一个字符,即下标从 1 1 1 开始。

  2. 给定一个范围 [ l ,   r ] [l,~r] [l, r],伤害为伤害串的这个范围内中字符 1 的个数

  3. 会修改伤害串中的数值,修改的方法是把 [ l ,   r ] [l,~r] [l, r] 中所有原来的字符 0 变成 1,将 1 变成 0

AKN 想知道一些时刻的伤害,请你帮助他求出这个伤害。

输入格式

输入的第一行有两个用空格隔开的整数,分别表示伤害串的长度 n n n,和操作的个数 m m m

输入第二行是一个长度为 n n n 的字符串 S S S,代表伤害串。

3 3 3 到第 ( m + 2 ) (m + 2) (m+2) 行,每行有三个用空格隔开的整数 o p , l , r op, l, r op,l,r。代表第 i i i 次操作的方式和区间,规则是:

  • o p = 0 op = 0 op=0,则表示将伤害串的 [ l ,   r ] [l,~r] [l, r] 区间内的 0 变成 11 变成 0
  • o p = 1 op = 1 op=1,则表示询问伤害串的 [ l ,   r ] [l,~r] [l, r] 区间内有多少个字符 1
输出格式

对于每次询问,输出一行一个整数,代表区间内 1 的个数。

样例 #1
样例输入 #1
10 6
1011101001
0 2 4
1 1 5
0 3 7
1 1 10
0 1 4
1 2 6
样例输出 #1
3
6
1
提示
样例输入输出 1 解释

原伤害串为 1011101001

对于第一次操作,改变 [ 2 ,   4 ] [2,~4] [2, 4] 的字符,伤害串变为 1100101001

对于第二次操作,查询 [ 1 ,   5 ] [1,~5] [1, 5]1 的个数,共有 3 3 3 个。

对于第三次操作,改变 [ 3 ,   7 ] [3,~7] [3, 7] 的字符,伤害串变为 1111010001

对于第四次操作,查询 [ 1 ,   10 ] [1,~10] [1, 10]1 的个数,共有 6 6 6 个。

对于第五次操作,改变 [ 1 ,   4 ] [1,~4] [1, 4] 的字符,伤害串变为 0000010001

对于第六次操作,查询 [ 2 ,   6 ] [2,~6] [2, 6]1 的个数,共有 1 1 1 个。

数据范围与约定

对于 10 % 10\% 10% 的数据,保证 n , m ≤ 10 n, m \leq 10 n,m10

另有 30 % 30\% 30% 的数据,保证 n , m ≤ 2 × 1 0 3 n, m \leq 2 \times 10^3 n,m2×103

对于 100 % 100\% 100% 的数据,保证 2 ≤ n , m ≤ 2 × 1 0 5 2 \leq n, m \leq 2 \times 10^5 2n,m2×105 0 ≤ o p ≤ 1 0 \leq op \leq 1 0op1 1 ≤ l ≤ r ≤ n 1 \leq l \leq r \leq n 1lrn S S S 中只含字符 0 和字符 1
线段树维护答案,只需将模板改成异或版即可
注意读入初始值时应用字符读入

部分代码如下

const int MAX = 2e5+10;
int tree[MAX<<4],a[MAX],tag[MAX<<4];
char s[MAX];
int n,m;
inline void Push_up(int rt){
	tree[rt] = tree[rt<<1]+tree[rt<<1|1];
}
inline void Push_down(int l,int r,int rt){
	if(!tag[rt]) return;
	int mid = (l+r)>>1;
	tag[rt<<1]^=1;
	tag[rt<<1|1]^=1;
	tree[rt<<1] = mid-l+1-tree[rt<<1];
	tree[rt<<1|1] = r-mid-tree[rt<<1|1];
	tag[rt] = 0;
}
void Build(int l,int r,int rt){
	if(l == r){
		tree[rt] = a[l];
		return;
	}
	int mid = (l+r)>>1;
	Build(l,mid,rt<<1);
	Build(mid+1,r,rt<<1|1);
	Push_up(rt);
}
void Update(int nl,int nr,int l,int r,int rt){
	Push_down(l,r,rt);
	if(l >= nl && r <= nr){
		tree[rt] = r-l+1-tree[rt];
		tag[rt]^=1;
		return; 
	}
	int mid = (l+r)>>1;
	if(nl <= mid) Update(nl,nr,l,mid,rt<<1);
	if(nr >=  mid+1) Update(nl,nr,mid+1,r,rt<<1|1);
	Push_up(rt);
}
int Query(int nl,int nr,int l,int r,int rt){
	if(l >= nl && r <= nr){
		return tree[rt];
	}
	Push_down(l,r,rt);
	int sum = 0;
	int mid = (l+r)>>1;
	if(nl <= mid) sum+=Query(nl,nr,l,mid,rt<<1);
	if(nr >= mid+1) sum+=Query(nl,nr,mid+1,r,rt<<1|1);
	return sum;
}

[TJOI2018]数学计算

题目描述

小豆现在有一个数 x x x,初始值为 1 1 1。小豆有 Q Q Q 次操作,操作有两种类型:

1 m:将 x x x 变为 x × m x \times m x×m,并输出 x   m o d   M x \bmod M xmodM

2 pos:将 x x x 变为 x x x 除以第 p o s pos pos 次操作所乘的数(保证第 p o s pos pos 次操作一定为类型 1,对于每一个类型 1 的操作至多会被除一次),并输出 x   m o d   M x \bmod M xmodM

输入格式

一共有 t t t 组输入。

对于每一组输入,第一行是两个数字 Q , M Q,M Q,M

接下来 Q Q Q 行,每一行为操作类型 o p op op,操作编号或所乘的数字 m m m(保证所有的输入都是合法的)。

输出格式

对于每一个操作,输出一行,包含操作执行后的 x   m o d   M x \bmod M xmodM 的值。

样例 #1
样例输入 #1
1
10 1000000000
1 2
2 1
1 2
1 10
2 3
2 4
1 6
1 7
1 12
2 7
样例输出 #1
2
1
2
20
10
1
6
42
504
84
提示

对于 20 % 20\% 20% 的数据, 1 ≤ Q ≤ 500 1 \le Q \le 500 1Q500

对于 100 % 100\% 100% 的数据, 1 ≤ Q ≤ 1 0 5 1 \le Q \le 10^5 1Q105 t ≤ 5 , M ≤ 1 0 9 t \le 5, M \le 10^9 t5,M109 0 < m ≤ 1 0 9 0 < m \leq 10^9 0<m109

线段树维护,树顶为答案

#include<bits/stdc++.h>
#define ll long long
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid ((l+r)>>1)
#define lson l,mid,ls
#define rson mid+1,r,rs
using namespace std;
const int MAX = 1e5+10;
int T;
ll Q,M;
ll tree[MAX<<2];
inline void Push_up(int rt){
	tree[rt] = tree[ls]*tree[rs]%M;
}
void Build(int l,int r,int rt){
	if(l == r){
		tree[rt] = 1;
		return;
	}
	tree[rt] = 1;
	Build(lson);
	Build(rson);
}
void Update(int l,int r,int rt,int pos,int val){
	if(l == r){
		tree[rt] = (val == 0) ? 1 : val;
		return;
	}
	if(mid >= pos) Update(lson,pos,val);
	else Update(rson,pos,val);
	Push_up(rt);
}
int main(){
	ios::sync_with_stdio(false);
	cin >> T;
	while(T--){
		cin >> Q >>M;
		Build(1,Q,1);
		for(int pos = 1;pos <= Q;pos++){
			int op,m;
			cin >> op >> m;
			if(op == 1){
				Update(1,Q,1,pos,m);
				cout << tree[1]%M << "\n";
			}
			else{
				Update(1,Q,1,m,0);
				cout << tree[1]%M << "\n";
			}
		}
	}
	return 0;
}

总结

线段树,是一种维护区间和的树形结构,能在 O ( l o g n ) O(logn) O(logn)进行区间修改以及查询

以上就是基本的线段树,动态开点,可持续化等拓展以后会更新

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值