1. 程式人生 > >動態開點永久標記線段樹的區間修改查詢

動態開點永久標記線段樹的區間修改查詢

#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;
		}
	}