线段树题集

区间和

题意简述
给定一数列,规定有两种操作,一是修改某个元素,二是求区间的连续和。
解题思路
线段树单点修改,区间查询的模板。

代码示例

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
typedef long long ll;
struct SegmentTree{
	int l,r;
	ll dat;
	#define l(x) t[x].l
	#define r(x) t[x].r
	#define dat(x) t[x].dat
}t[N*4];
int n,m;
void BuildTree(int rt,int l,int r){
	/*为区间[l,r]建立以rt为根节点的子树*/
	l(rt) = l, r(rt) = r,dat(rt) = 0;
	if(l >= r) return;
	int mid = l+r>>1;
	BuildTree(rt*2,l,mid);
	BuildTree(rt*2+1,mid+1,r);
} 
void Add(int rt,int p,int val){
	if(l(rt) == r(rt)){
		dat(rt) += val; return;
	}
	int mid = l(rt) + r(rt) >> 1;
	if(p <= mid) Add(rt*2,p,val);
	else Add(rt*2+1,p,val);
	dat(rt) = dat(rt*2) + dat(rt*2+1);
}
ll ask(int rt,int l,int r){
	if(l(rt) == l && r(rt) == r) return dat(rt);
	int mid = l(rt) + r(rt)>>1;
	if(l > mid) return ask(rt*2+1,l,r);
	else if(r <= mid) return ask(rt*2,l,r);
	else{
		return ask(rt*2,l,mid)+ask(rt*2+1,mid+1,r);
	}
}
int main(){
	//freopen("123.txt","r",stdin);
	scanf("%d%d",&n,&m);
	BuildTree(1,1,n);
	for(int i = 1,k,a,b;i <= m;i++){
		scanf("%d%d%d",&k,&a,&b);
		if(k) printf("%lld\n",ask(1,a,b));
		else Add(1,a,b);
	}
	return 0;
}

A Simple Problem with Integers

题意简述
这是一道模板题。

给定数列 a[1],a[2],…,a[n],你需要依次进行 q 个操作,操作有两类:

C l r x:给定 l,r,x,对于所有 i∈[l,r],将 a[i] 加上 x(换言之,将 a[l],a[l+1],…,a[r] 分别加上 x);

Q l r:给定 l,r,求 ∑ri=la[i] 的值(换言之,求 a[l]+a[l+1]+⋯+a[r] 的值)。
解题思路
这题也是POJ3468。可以用很多种方法解决,是个练手的模板题。线段树区间修改+区间查询,延迟标记的简单使用。

代码示例

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
typedef long long ll;
struct SegmentTree{
	int l,r;
	ll sum,add;
	#define l(x) t[x].l
	#define r(x) t[x].r
	#define sum(x) t[x].sum
	#define add(x) t[x].add
}t[N*4];
int n,m,a[N];
void BuildTree(int rt,int l,int r){
	l(rt) = l, r(rt) = r;
	if(l == r){
		sum(rt) = a[l]; return;
	}
	int mid = l+r>>1;
	BuildTree(rt*2,l,mid); BuildTree(rt*2+1,mid+1,r);
	sum(rt) = sum(rt*2) + sum(rt*2+1);
} 
void spread(int p){
	/*将延迟标记向下传一层*/
	if(!add(p)) return;
	sum(p*2) += (r(p*2)-l(p*2)+1)*add(p);
	sum(p*2+1) += (r(p*2+1)-l(p*2+1)+1)*add(p);
	add(p*2) += add(p); add(p*2+1) += add(p);
	add(p) = 0;
}
void change(int rt,int l,int r,int val){
	/*[l,r]内所有位置上元素都+val*/ 
	if(l <= l(rt) && r(rt) <= r){
		sum(rt) += (r(rt)-l(rt)+1)*val;
		add(rt) += val; return;
	}
	spread(rt);
	int mid = l(rt)+r(rt)>>1;
	if(l <= mid) change(rt*2,l,r,val);
	if(r > mid) change(rt*2+1,l,r,val);
	sum(rt) = sum(rt*2) + sum(rt*2+1);
}
ll ask(int rt,int l,int r){
	/*回答[l,r]区间和*/ 
	if(l <= l(rt) && r(rt) <= r) return sum(rt);
	spread(rt); ll res = 0;
	int mid = l(rt)+r(rt)>>1;
	if(l <= mid) res += ask(rt*2,l,r);
	if(r > mid) res += ask(rt*2+1,l,r);
	return res;
}
int getInt(){
	int ans = 0;
	bool neg = false;
	char c = getchar();
	while(c != '-' && (c < '0' || c > '9')) c = getchar();
	if(c == '-') neg = true, c = getchar();
	while(c >= '0' && c <= '9') ans = ans*10+c-'0', c = getchar();
	return neg?-ans:ans;
}
int main(){
//	freopen("123.txt","r",stdin);
	n = getInt(); m = getInt();
	for(int i = 1;i <= n;i++) a[i] = getInt();
	BuildTree(1,1,n);
	char op[4];
	for(int i = 1,l,r,x;i <= m;i++){
		scanf("%s",op);
		if(op[0] == 'C'){
			l = getInt(); r = getInt(); x = getInt();
			change(1,l,r,x);
		}else {
			l = getInt(); r = getInt();
			printf("%lld\n",ask(1,l,r));
		}
	} 
}

最大数

题意简述
原题来自:JSOI 2008

给定一个正整数数列 a1,a2,a3,⋯,an ,每一个数都在 0∼p–1 之间。可以对这列数进行两种操作:

添加操作:向序列后添加一个数,序列长度变成 n+1;

询问操作:询问这个序列中最后 L 个数中最大的数是多少。

程序运行的最开始,整数序列为空。写一个程序,读入操作的序列,并输出询问操作的答案。
解题思路
因为最多有N个数,那么先根据[1 , N]建立线段树,提前把位置空出来就好了,now指针记录最后一个位置,每次把新加入的元素插入++now,而询问最后L个数的最大值,则用ask(now - L , now)来查询即可,其它的就是线段树上维护区间最大值。

代码示例

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
typedef long long ll;
struct SegmentTree{
	int l,r;
	ll mx;
	#define l(x) t[x].l
	#define r(x) t[x].r
	#define mx(x) t[x].mx
}t[N*4];
void BuildTree(int rt,int l,int r){
	l(rt) = l, r(rt) = r, mx(rt) = 0;
	if(l == r) return;
	int mid = l+r>>1;
	BuildTree(rt*2,l,mid); BuildTree(rt*2+1,mid+1,r);
}
void change(int rt,int p,int val){
	if(l(rt) == r(rt)){
		mx(rt) = val; return;
	}
	int mid = l(rt)+r(rt)>>1;
	if(p > mid) change(rt*2+1,p,val);
	else change(rt*2,p,val);
	mx(rt) = max(mx(rt*2),mx(rt*2+1));
}
int ask(int rt,int l,int r){
	if(l <= l(rt) && r(rt) <= r) return mx(rt);
	int mid = l(rt) + r(rt)>>1 , res = 0;
	if(l <= mid) res = ask(rt*2,l,r);
	if(r > mid) res = max(res,ask(rt*2+1,l,r));
	return res;
}
int m,p,ans,now;
int main(){
	//freopen("123.txt","r",stdin);
	scanf("%d%d",&m,&p);
	BuildTree(1,1,N-1);
	char op[4];
	for(int i = 1,x;i <= m;i++){
		scanf("%s%d",op,&x);
		if(op[0] == 'A') change(1,++now,(x*1ll+ans)%p);
		else printf("%lld\n",ans = ask(1,now-x+1,now));
	}
	return 0;
}

花神游历各国*

题意简述
原题来自:BZOJ 3211

花神喜欢步行游历各国,顺便虐爆各地竞赛。花神有一条游览路线,它是线型的,也就是说,所有游历国家呈一条线的形状排列,花神对每个国家都有一个喜欢程度(当然花神并不一定喜欢所有国家)。

每一次旅行中,花神会选择一条旅游路线,它在那一串国家中是连续的一段,这次旅行带来的开心值是这些国家的喜欢度的总和,当然花神对这些国家的喜欢程序并不是恒定的,有时会突然对某些国家产生反感,使他对这些国家的喜欢度 δ 变为 δ \sqrtδ δ (可能是花神虐爆了那些国家的 OI,从而感到乏味)。

现在给出花神每次的旅行路线,以及开心度的变化,请求出花神每次旅行的开心值。

解题思路
我刚开始想的是如何把修改延迟,但是后来发现不太容易实现。很容易的发现的就是1e9开根10次后就是1了(当然还有下取整),所以题目范围内所有数最多开根10次就会变成1或0,也就是说我们可以记录每个点被修改了多少次,如果已经修改了10次以上,那么再修改也无意义了。
于是我们可以每次修改都修改到叶子节点(因为无法延迟或整段修改),同时记录每个节点被修改了多少次,如果一段区间修改次数最小的节点都修改了10次,那么这一段区间就都不用修改了;这就会大大降低时间复杂度,因为每一个节点最多被修改10次,O(10NlogN)。

代码示例

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
typedef long long ll;
struct SegmentTree{
	int l,r,mi;
	ll sum;
	#define l(x) t[x].l
	#define r(x) t[x].r
	#define mi(x) t[x].mi
	#define sum(x) t[x].sum
}t[N*4];
int n,m,a[N];
void BuildTree(int rt,int l,int r){
	l(rt) = l,r(rt) = r,mi(rt) = 0;
	if(l == r){
		sum(rt) = a[l]; return;
	}
	int mid = l+r>>1;
	BuildTree(rt*2,l,mid); BuildTree(rt*2+1,mid+1,r);
	sum(rt) = sum(rt*2) + sum(rt*2+1);
}
void change(int rt,int l,int r){
	if(mi(rt) > 10) return;
	if(l(rt) == r(rt)){
		sum(rt) = a[l(rt)] = sqrt(a[l(rt)]); 
		mi(rt)++; return;
	}
	int mid = l(rt) + r(rt) >> 1;
	if(l <= mid) change(rt*2,l,r);
	if(r > mid) change(rt*2+1,l,r);
	sum(rt) = sum(rt*2) + sum(rt*2+1);
	mi(rt) = min(mi(rt*2),mi(rt*2+1));
}
ll ask(int rt,int l,int r){
	if(l <= l(rt) && r(rt) <= r) return sum(rt);
	int mid = l(rt)+r(rt)>>1; ll res = 0;
	if(l <= mid) res += ask(rt*2,l,r);
	if(r > mid) res += ask(rt*2+1,l,r);
	return res;
}
int main(){
	//freopen("123.txt","r",stdin);
	scanf("%d",&n);
	for(int i = 1;i <= n;i++) scanf("%d",a+i);
	BuildTree(1,1,n);
	scanf("%d",&m);
	for(int i = 1,op,l,r;i <= m;i++){
		scanf("%d%d%d",&op,&l,&r);
		if(op == 1) printf("%lld\n",ask(1,l,r));
		else change(1,l,r);
	}
	return 0;
}

维护序列*

题意简述
原题来自:AHOI 2009

老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。

有长为 n 的数列,不妨设为 a1,a2,⋯,an 。有如下三种操作形式:

把数列中的一段数全部乘一个值;

把数列中的一段数全部加一个值;

询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模 P 的值。

解题思路
这个如果修改操作只有 2 或 1,那么就是一个区间修改-延迟标记的模板题,但是它把“乘”和“加”操作放在了一起,当然还是需要用延迟标记来做,add表示待加上的值,mul表示待乘的值。
有一个问题,就是我们在向下传递延迟标记时,是先传递加法呢,还是乘法呢?其实问题的根源不在这,而在于我们在做乘法时,对加法的延迟标记也要乘 val,即 add = add * val,因为满足结合律。搞完这点剩下就是写起来比较麻烦而已,没别的难点了

代码示例

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
typedef long long ll;
struct SegmentTree{
	int l,r;
	ll add,mul,sum;
	#define l(x) t[x].l
	#define r(x) t[x].r
	#define mul(x) t[x].mul
	#define add(x) t[x].add
	#define sum(x) t[x].sum
}t[N*4];
int n,m,P;
ll a[N];
void BuildTree(int rt,int l,int r){
	l(rt) = l, r(rt) = r, add(rt) = 0, mul(rt) = 1;
	if(l == r){
		sum(rt) = a[l]; return;
	}
	int mid = l+r>>1;
	BuildTree(rt*2,l,mid); BuildTree(rt*2+1,mid+1,r);
	sum(rt) = sum(rt*2) + sum(rt*2+1);
}
void spread_add(int p){
	/*将累加标记向下传一层*/
	if(!add(p)) return;
	sum(p*2) = (sum(p*2) + (r(p*2) - l(p*2)+1)*add(p)%P)%P;
	sum(p*2+1) = (sum(p*2+1) + (r(p*2+1) - l(p*2+1)+1)*add(p)%P)%P;
	add(p*2) = (add(p*2) + add(p))%P; 
	add(p*2+1) = (add(p*2+1)+add(p))%P; 
	add(p) = 0;
}
void spread_mul(int p){
	/*将累乘标记向下传一层,累乘标记对累加标记也有影响*/ 
	if(mul(p) == 1) return;
	sum(p*2) = sum(p*2)*mul(p)%P;
	sum(p*2+1) = sum(p*2+1)*mul(p)%P;
	mul(p*2) = mul(p*2)*mul(p)%P;
	mul(p*2+1) = mul(p*2+1)*mul(p)%P;
	/*更新add标记*/ 
	add(p*2) = add(p*2)*mul(p)%P;
	add(p*2+1) = add(p*2+1)*mul(p)%P; 
	mul(p) = 1;
}
void Mul(int rt,int l,int r,ll val){
	if(l <= l(rt) && r(rt) <= r){
		add(rt) = add(rt)*val%P;//更新add标记! 
		sum(rt) = sum(rt)*val%P;
		mul(rt) = mul(rt)*val%P;
		return;
	}
	spread_mul(rt);spread_add(rt); 
	int mid = l(rt)+r(rt)>>1;
	if(l <= mid) Mul(rt*2,l,r,val);
	if(r > mid) Mul(rt*2+1,l,r,val);
	sum(rt) = (sum(rt*2) + sum(rt*2+1))%P;
}
void Add(int rt,int l,int r,ll val){
	if(l <= l(rt) && r(rt) <= r){
		sum(rt) = (sum(rt) + (r(rt)-l(rt)+1)*val % P)%P;
		add(rt) = (add(rt)+val)%P; return;
	}
	spread_mul(rt); spread_add(rt);
	int mid = l(rt)+r(rt)>>1;
	if(l <= mid) Add(rt*2,l,r,val);
	if(r > mid) Add(rt*2+1,l,r,val);
	sum(rt) = (sum(rt*2) + sum(rt*2+1))%P;
}
ll ask(int rt,int l,int r){
	if(l <= l(rt) && r(rt) <= r) return sum(rt)%P;
	spread_mul(rt); spread_add(rt) ;
	ll res = 0;
	int mid = l(rt)+r(rt)>>1;
	if(l <= mid) res += ask(rt*2,l,r);
	if(r > mid) res += ask(rt*2+1,l,r);
	return res%P;
}
int main(){
	scanf("%d%d",&n,&P);
	for(int i = 1;i <= n;i++) scanf("%lld",a+i), a[i] %= P;
	BuildTree(1,1,n);
	scanf("%d",&m);
	for(int i = 1,op,t,g,c;i <= m;i++){
		scanf("%d",&op);
		if(op == 1){
			scanf("%d%d%d",&t,&g,&c);
			Mul(1,t,g,c);
		}else if(op == 2){
			scanf("%d%d%d",&t,&g,&c);
			Add(1,t,g,c);
		}else{
			scanf("%d%d",&t,&g);
			printf("%lld\n",ask(1,t,g));
		}
	}
	return 0;
}
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 程序猿惹谁了 设计师:白松林 返回首页