动态开点永久标记线段树的区间修改查询

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define ll long long
using namespace std;
struct node{
	long long l,r,sum;
}t[10000050];
void read(ll &x)
{
    int f=1;x=0;char c=getchar();
    while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();} 
	while(c<='9'&&c>='0'){x=10*x+c-'0';c=getchar();} 
	x=x*f;
} 
ll cnt;ll now=0;
ll n,m;
ll lazy[10000050];
void insert(ll &now,ll l1,ll r1,ll x,ll y)
{
	if(now==0)
	now=++cnt;//还没有节点,要新建一个 
	t[now].sum+=y;//这个节点表示的区间含有第x位数,所以它的值(区间和)加上第x位数也就是y 
	if(l1==r1)return;//已经更新到了子节点于是结束 
	int mid=(l1+r1)/2;//判断x在当前节点下的左子节点还是右子节点 
	if(x<=mid)insert(t[now].l,l1,mid,x,y);//在左子节点
	else insert(t[now].r,mid+1,r1,x,y); //右子节点 
}//单点插入&建树
ll pd(ll l1,ll r1,ll p,ll q)
{
	ll r=min(r1,q);
	ll l=max(l1,p);
	return r-l+1;
}
void insert2(ll &now,ll l1,ll r1,ll p,ll q,ll y)
{
	if(now==0)
	now=++cnt;//没节点加一个节点 
	t[now].sum+=y*pd(l1,r1,p,q);//1
	if(p<=l1&&q>=r1)
	//如果[p,q]已经包含了当前节点的全部范围 那么显然当前节点中每一个值都要修改,也就是说它的所有子孙节点都改。
	//而我们懒得改(会T)所以在这个范围最大的被完全包含的节点处记录lazy
	//然后显然就不需要再改下面的了~\(≧▽≦)/~ 
	{
		lazy[now]+=y;
		return;
	}
	int mid=(l1+r1)/2;//判断[p,q]包含当前节点下的左子节点还是右子节点 
	if(p<=mid)insert2(t[now].l,l1,mid,p,q,y);//左 
	if(q>mid)insert2(t[now].r,mid+1,r1,p,q,y); //右 
	//1:
	//由带有~\(≧▽≦)/~的那一段注释易知 如果[p,q]已经包含了当前节点的全部范围,那么它以下的所有节点暂时不予处理
	//而同样易知它及它以上的节点不改是不行的。
	//怎么改呢??(假装沉思)
	//因为是它及它以上,所以可能大于[p,q]也可能小于等于
	//那么这一段的值总共要加上[l1,r1]与[p,q]的(重合部分的数据的个数)乘以(每个数要加的数(y))//()用来断句。 
}
ll findd(ll now,ll l1,ll r1,ll x,ll y)
{
	if(l1>=x&&r1<=y)
	{
		return t[now].sum;
	}//如果这个区间[l1,r1]已经被所求区间[x,y]包含,那么易知这个区间[l1,r1]的值(即区间内所有的数值的和)是[x,y]的和的一部分
	//然后就可以直接把它加入到所求结果([x,y]的和)里
	//·2:lazytag呢??下面的点呢?O(∩_∩)O~ 
	ll mid=(l1+r1)/2;
	if(lazy[now])//如果当前的这个节点上有lazy值
	{
		if(!t[now].l)//因为动态开点,所以有可能这个节点有lazy值,但它根本没有子节点 
		t[now].l=++cnt;//造一个
		lazy[t[now].l]+=lazy[now];//有可能它之前已经有过有过lazy,又传给它一个,那么它对自己的子孙节点的影响(lazy)就是二次之和 
		t[t[now].l].sum+=lazy[now]*(mid-l1+1);//(mid-ll+1)是指左子节点的数据个数。
		//由上面的操作易知如果now处有lazy,那么它的所有子节点都会有一个(子节点区间内数据个数*lazy值)的值要加,所以查询的时候,把它加进去,才可以进入下一层
		//上面那个带O(∩_∩)O~的问题的ans
		//这里我们把下一层递归里的now(也就是这里的子节点)的值已经加好,想用的话可以直接取
		//而第一次进入这个dfs时的O(∩_∩)O~语句,其区间是1到n,是肯定处理好了的 (原因见57到59行) 
		//那么如果 下一层递归里的now(也就是这里的子节点)已经被所求节点完全包含,那么它的子孙节点就没必要求了 
		if(!t[now].r)
		t[now].r=++cnt;
		lazy[t[now].r]+=lazy[now];
		t[t[now].r].sum+=lazy[now]*(r1-mid);//这四行和上四行是没什么区别的因为就连代码我都是复制的qwq 
		lazy[now]=0;//now的lazy已经发下去了,就把它的lazy清零,不能再发一次 
	}
	ll suml=0;//由now分出的左边的值
	ll sumr=0;//右边
	if(x<=mid)suml=findd(t[now].l,l1,mid,x,min(mid,y)); 
	//如果所求区间的左边小于等于mid也就是在now的左子节点有一部分值就去dfs左子节点(不管右边怎么样) 
	//而往左右子节点分的时候以mid为分界线,所以这里的子区间右端点最大只能是mid(要不白分了啊qwq)
	//当然如果所求的区间右端点比mid小,那肯定不能取mid,要不所求区间都变了 ,所以取一个min就可以了 
	if(y>mid)sumr=findd(t[now].r,mid+1,r1,max(mid+1,x),y);
	//同上
	//这样偏右部分的往右子分,偏左的 往左子分,分分分分,然后把和全加起来就完了。 
	 return suml+sumr;//撒花~~ 
}
int main()
{
	ll mm;
	read(n);read(m);
	ll root=0;
	for(ll i=1;i<=n;i++)
	{
		read(mm);
		insert(root,1,n,i,mm);
	}
	for(int i=1;i<=m;i++)
	{
		ll o;
		read(o);
		if(o==1)
		{
			ll x;ll y;ll mmm;
			read(x);read(y);read(mmm);
			insert2(root,1,n,x,y,mmm);
		}
		if(o==2)
		{
			ll x;ll y;
			int mmm;
			read(x);read(y);
			cout<<findd(root,1,n,x,y)<<endl;
		}
	}

猜你喜欢

转载自blog.csdn.net/weixin_42759798/article/details/85845991