线段树--求区间两两乘积

题目链接

链接:线段树

题意:

现在给你一串数字,a1,a2,a3…an,你需要进行m次如下几种操作:

1 l r v,区间a[l]到a[r]的所有数字全部加v。

2 l r v,区间a[l]到a[r]的所有数字全部乘v。

3 l r,求区间a[l]到a[r]之间两两之间数字的乘积和(例如:2,3,4,5两两之间乘积和为 2*3+2*4+2*5+3*4+3*5+4*5)

现在给出你操作的顺序,按照要求操作或者输出。

思路:

对区间维护

\sum_{l}^{r}x_i   \sum_{i=l}^{r}\sum_{j=i+1}^{r} x_ix_j

lazy维护 进行修改后x\rightarrowax+b,维护a,b。

tag_union:

(用于对区间lazy的下传,子区间原来就有lazy,将区间传下来的lazy与子区间的lazy合并)

到当前区间有lazy a_1,b_1,子区间(u<<1,u<<1|1)有lazy a_2,b_2

a_1的下传有  a_2=a_1*a_2 , b_2=a_1*b_2

b_1的下传有  b_2=b_1+b_2

cal_lazy:

\sum_{l}^{r}x_i\rightarrow \sum_{l}^{r}(ax_i+b)=(r-l+1)*b+a\sum_{l}^{r}x_i,sum[0]=\sum_{l}^{r}(ax_i+b)

lazy的修改时,sum[0]=\sum_{l}^{r}(ax_i+b)\rightarrow a_1\sum_{l}^{r}(a_2x_i+b_2)+b_1*(r-l+1)

\sum_{i=l_1}^{r_1}\sum_{j=i+1}^{r_1}x_ix_j  和\sum_{i=l_2}^{r_2}\sum_{j=i+1}^{r_2}x_ix_j在进行合并时就等于sum[1]=\sum_{i=l_1}^{r_1}\sum_{j=i+1}^{r_1}x_ix_j+\sum_{i=l_2}^{r_2}\sum_{j=i+1}^{r_2}x_ix_j+\sum_{i=l_1}^{r_1}x_i\cdot \sum_{j=l_2}^{r_2}x_j

=lsum[1]+rsum[1]+lsum[0]*rsum[0]

lazy的修改时,相当于每个x较原来都乘a_1b_1,进一步推sum[1]\rightarrow a_1^2sum[1]+(len-1)*a_1b_1*sum[0]+(len-1)*len/2*b_1^2

 代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N=1e5+10;
int t,n,m,p;
typedef struct Node{
	int l,r;
	ll ki[2];
	ll sum[2];//sum[0]维护区间加和,sum[1]维护区间两两乘积 
	friend Node operator +(const Node &x,const Node &y){
		Node z;
		z.sum[0]=(x.sum[0]+y.sum[0])%p;
		z.sum[1]=((x.sum[1]+y.sum[1])%p+
				x.sum[0]*y.sum[0]%p)%p;
		return z;
	} 
}Node;
Node tr[N*4];
int a[N];
void init_lazy(int u){
	tr[u].ki[0]=0;
	tr[u].ki[1]=1;
}
void pushup(int u){
	tr[u].sum[0]=(tr[u<<1].sum[0]+tr[u<<1|1].sum[0])%p;
	tr[u].sum[1]=((tr[u<<1].sum[1]+tr[u<<1|1].sum[1])%p+
				tr[u<<1].sum[0]*tr[u<<1|1].sum[0]%p)%p;
}
void build(int u,int l,int r){
	tr[u].l=l,tr[u].r=r;
	init_lazy(u);
	if(l==r){
		tr[u].sum[0]=a[l];
		tr[u].sum[1]=0;
		return ;
	}
	int mid=(l+r)>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}

void cal_lazy(int fa,int ch){
	ll a=tr[fa].ki[1],b=tr[fa].ki[0];
	ll len=tr[ch].r-tr[ch].l+1;
	tr[ch].sum[1]=(a*a%p*tr[ch].sum[1]%p+a*b%p*(len-1)%p*tr[ch].sum[0]%p+(len-1)*len/2%p*b%p*b%p)%p;
	tr[ch].sum[0]=(a*tr[ch].sum[0]%p+len*b%p)%p;
}
void tag_union(int fa,int ch){
	ll a=tr[fa].ki[1],b=tr[fa].ki[0];
	tr[ch].ki[1]=(tr[ch].ki[1]*a)%p;
	tr[ch].ki[0]=(tr[ch].ki[0]*a%p+b)%p;
}
void pushdown(int u){
	if(tr[u].ki[0]!=0||tr[u].ki[1]!=1){
		cal_lazy(u,u<<1);
		cal_lazy(u,u<<1|1);
		tag_union(u,u<<1);
		tag_union(u,u<<1|1);
		init_lazy(u);
	}
}
void modify(int u,int l,int r,ll k,int type){
	if(tr[u].l>=l&&tr[u].r<=r){
		if(type==1){
			ll len=tr[u].r-tr[u].l+1;
			tr[u].sum[1]=(tr[u].sum[1]+k*(len-1)%p*tr[u].sum[0]%p+len*(len-1)/2%p*k%p*k%p)%p;
			tr[u].sum[0]=(tr[u].sum[0]+k*len%p)%p;
			tr[u].ki[0]=(tr[u].ki[0]+k)%p;
		}
		else{
			tr[u].sum[1]=(tr[u].sum[1]*k%p*k)%p;
			tr[u].sum[0]=(tr[u].sum[0]*k)%p;
			tr[u].ki[1]=(tr[u].ki[1]*k)%p;
			tr[u].ki[0]=(tr[u].ki[0]*k)%p;
		}
		return ;
	}
	pushdown(u);
	int mid=(tr[u].l+tr[u].r)>>1;
	if(l<=mid)
		modify(u<<1,l,r,k,type);
	if(r>mid)
		modify(u<<1|1,l,r,k,type);
	pushup(u);
}
Node query(int u,int l,int r){
	if(tr[u].l>=l&&tr[u].r<=r)
		return tr[u];
	pushdown(u);
	int mid=(tr[u].l+tr[u].r)>>1;
	Node res;
	if(l>mid){
		return query(u<<1|1,l,r);
	}
	else if(r<=mid){
		return query(u<<1,l,r);
	}	
	else{
		res=query(u<<1,l,r)+query(u<<1|1,l,r);
		return res;
	}
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d%d",&n,&m,&p);
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
		}
		build(1,1,n);
		int opt,l,r,v;
		for(int i=1;i<=m;i++){
			scanf("%d%d%d",&opt,&l,&r);
			if(opt<=2){
				scanf("%d",&v);
				modify(1,l,r,v,opt);
			}
			else{
				Node res=query(1,l,r);
				printf("%lld\n",res.sum[1]);
			}
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值