P2357 守墓人(树状数组/线段树)

版权声明:如果路过的各位大佬想对菜鸡进行指点请加QQ:3360769137 https://blog.csdn.net/PleasantlY1/article/details/84671828

题目描述

在一个荒凉的墓地上

有一个令人尊敬的守墓人, 他看守的墓地从来

没有被盗过, 所以人们很放心的把自己的先人的墓

安顿在他那

守墓人能看好这片墓地是必然而不是偶然.....

因为....守墓人懂风水 0.0

他把墓地分为主要墓碑和次要墓碑, 主要墓碑

只能有 1 个, 守墓人把他记为 1 号, 而次要墓碑有

n-1 个,守墓人将之编号为 2,3...n,所以构成了一个有 n 个墓碑的墓地。

而每个墓碑有一个初始的风水值,这些风水值决定了墓地的风水的好坏,所以守墓人

需要经常来查询这些墓碑。

善于运用风水的守墓人,通过一次次逆天改命,使得自己拥有了无限寿命,没人知道

他活了多久。

这天,你幸运的拜访到了他,他要求你和他共同见证接下来几年他的战果,但不过他

每次统计风水值之和都需要你来帮他计算,算错了他会要你命 QAQ

风水也不是不可变,除非遭遇特殊情况,已知在接下来的 2147483647 年里,会有 n 次

灾难,守墓人会有几个操作:

1.将[l,r]这个区间所有的墓碑的风水值增加 k。

2.将主墓碑的风水值增加 k

3.将主墓碑的风水值减少 k

4.统计[l,r]这个区间所有的墓碑的风水值之和

5.求主墓碑的风水值

上面也说了,很多人会把先人的墓安居在这里,而且守墓人活了很多世纪→_→,墓碑

的数量会多的你不敢相信= =

守墓人和善的邀请你帮他完成这些操作,要不然哪天你的旅馆爆炸了,天上下刀子.....

为了活命,还是帮他吧

输入输出格式

输入格式:

第一行,两个正整数 n,f 表示共有 n 块墓碑,并且在接下来的

2147483647 年里,会有 f 次世界末日

第二行,n 个正整数,表示第 i 块墓碑的风水值

接下来 f 行,每行都会有一个针对世界末日的解决方案,如题所述,标记同题

输出格式:

输出会有若干行,对 4 和 5 的提问做出回答

输入输出样例

输入样例#1: 

5 7
0 0 0 0 0
1 1 5 1
1 1 3 3
2 3
3 1
4 1 5
2 1
5

输出样例#1: 

16
7

说明

20%的数据满足:1≤n≤100

50%的数据满足:1≤n≤6000

100%的数据满足:1≤n,f≤2*10^5

思路:这不是裸的线段树吗?但是我不会啊........那就用树状数组来搞吧。我的上篇博客用树状数组实现了线段树的区间修改区间查询功能,那就直接拿到这道题上用用。该题需要实现单点查询、区间查询、区间修改、单点修改功能,但是我用树状数组只实现了区间与单点查询。还差一个单点修改功能,另开一个数组实现吗?注意基础功能实现是建立在树状数组单点修改区间查询的基础上的,所以tree数组中存储的是差分值,所以把单点以区间的方式处理,注意需要修改数组p中tree[i]*(i-1)的前缀和。

加输入挂用时252ms,还行。

树状数组代码如下:

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<iostream>
using namespace std;
#define INF 0x3f3f3f3f
#define ll long long
#define per(i,a,b) for(long long int i=a;i<=b;++i)
ll int n,m,tree[2000010],p[2000010];
ll int lowbit(ll int k)
{
	return k&(-k);
}
void add(ll int x,ll int k,int fag)
{
	while(x<=n)
	{
		if(fag==1) tree[x]+=k;
		else p[x]+=k;
		x+=lowbit(x);
	}
}
ll int sum(ll int x,int fag)
{
	ll int s=0;
	while(x!=0)
	{
		if(fag==1) s+=tree[x];
		else s+=p[x];
		x-=lowbit(x);
	}
	return s;
}
int main()
{
	scanf("%lld%lld",&n,&m);
	ll int x,y=0;
	per(i,1,n) 
	{
		//ll int x,y;
	    scanf("%lld",&x);
	    y=x-y;
	    add(i,y,1);//区间查询单点修改,前缀和为该位置的值 
	    add(i,(i-1)*y,2);//前缀和差值部分 
	    y=x;
	} 
	per(i,1,m)
	{
		ll int a,b,c,d;
		scanf("%lld",&a);
		if(a==1)
		{
			scanf("%lld%lld%lld",&b,&c,&d);
			add(b,d,1);add(c+1,-d,1);//正常的插入 
			add(b,d*(b-1),2);add(c+1,-d*c,2);//正常的维护 
		}
		if(a==2) 
		{
			scanf("%lld",&b);
			add(1,b,1);add(2,-b,1);
			add(2,-b,2);
		}
		if(a==3)
		{
			scanf("%lld",&b);
			add(1,-b,1);add(2,b,1);
			add(2,b,2);
		}
		if(a==4)
		{
			scanf("%lld%lld",&b,&c);
			printf("%lld\n",(c*sum(c,1)-(b-1)*sum(b-1,1))-(sum(c,2)-sum(b-1,2)));
		}
		if(a==5) printf("%lld\n",sum(1,1));
	}
	return 0;
}

线段树代码如下:

猜你喜欢

转载自blog.csdn.net/PleasantlY1/article/details/84671828