链接:https://ac.nowcoder.com/acm/contest/26896/1008
来源:牛客网

题目描述

qn姐姐最好了~
qn姐姐给你了一个长度为n的序列还有m次操作让你玩,
1 l r 询问区间[l,r]内的元素和
2 l r 询问区间[l,r]内的元素的平方
3 l r x 将区间[l,r]内的每一个元素都乘上x
4 l r x 将区间[l,r]内的每一个元素都加上x

输入描述:

第一行两个数n,m
接下来一行n个数表示初始序列
就下来m行每行第一个数为操作方法opt,
若opt=1或者opt=2,则之后跟着两个数为l,r
若opt=3或者opt=4,则之后跟着三个数为l,r,x
操作意思为题目描述里说的

输出描述:

对于每一个操作1,2,输出一行表示答案

吐槽:

这一题完全可能出现样例/自己捏的小样例没过,但是能够AC的情况(我第一次提交时就是这样,自己都没想到能过),只能说数据太水了......但是题目还是一道非常好的基础题

题型:

线段树--区间修改与区间查询+一点基础数学

思路(请在掌握基本的线段树区间修改与区间和查询后再看):

主要难点其实就两个,一是区间的乘法,二是区间的平方和查询,另外两个想必学过一点线段树的区间修改与查询就都可以写出来。
区间的乘法,很显然,要想让lazy既存储加法,又存储乘法,是无法做到的,因此考虑用lazy和lazy2来分别存储加法,乘法的标记
我们知道,对于一个数,它无论是先乘后加,还是先加后乘,都可以表示成kx+b的形式。但是两种形式也有区别,如果先乘a后加b,那么得到的是ax+b,如果先加b后乘a,那么得到的是a(x+b)=ax+ab
可以看出,对于加法而言(+a),其对于lazy(加法标记)具有贡献(+a),对于lazy2(乘法标记)则不具有贡献;而对于乘法而言(*a),其对于lazy2(乘法标记)具有贡献(*a),对于lazy(加法标记)也具有贡献(*a)。
所以乘法的事情多开一个lazy2即可解决,注意lazy2初始化为1,区间和直接为ax+b
接下来是区间的平方和
核心公式:x^2——>(ax+b)^2=a*a*x^2+2*a*b*x+b^2
根据这个公式就可以推算出区间平方和了
设tree[]为区间和,tree2[]为区间平方和,那么可得tree2[]=lazy2[]*lazy2[]*tree2[]+2*lazy2[]*lazy[]*tree[]+lazy[]*lazy[];
由此,此题的两个难点已经基本解决,剩下的就是套模板了,难度:属于基础题,但是对于像我这样的小白能够锻炼线段树的基础思维,还是不错的
代码如下,是正确的,一楼那位大佬捏的数据也能够过,而且个人感觉代码应该挺清晰的(借鉴了雨巨大大的模板),适合和自己一样0基础的小白食用。

代码:

#include<bits/stdc++.h>
#define int long long int
using namespace std;
const int N=20000;
int op;
int tree[N*4],a[N],lazy[N*4],lazy2[N*4],tree2[N*4];//lazy:标记(需要时沿着递归顺着往下走就行)
//lazy标记内存储的值是这个节点自己知道,并且其子节点不知道的修改的值总和 
void build(int p,int l,int r){
	if(l==r){ //区间已经变成单点了 
		tree[p]=a[l];
		tree2[p]=a[l]*a[l];
		return;
	}
	int mid=(l+r)/2;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
	tree[p]=tree[p*2]+tree[p*2+1]; //对区间求和 
	tree2[p]=tree2[p*2]+tree2[p*2+1]; //对区间求平方和 
}

void pushdown(int p,int l,int r){
	int mid=(l+r)/2;
	lazy2[p*2]*=lazy2[p];
	lazy2[p*2+1]*=lazy2[p];
	lazy[p*2]=(lazy[p*2]*lazy2[p])+lazy[p];
	lazy[p*2+1]=(lazy[p*2+1]*lazy2[p])+lazy[p];
	tree2[p*2]=(lazy2[p]*lazy2[p]*tree2[p*2])+(2*tree[p*2]*lazy[p]*lazy2[p]+(mid-l+1)*lazy[p]*lazy[p]);
	tree2[p*2+1]=(lazy2[p]*lazy2[p]*tree2[p*2+1])+(2*tree[p*2+1]*lazy[p]*lazy2[p]+(r-(mid+1)+1)*lazy[p]*lazy[p]);
	tree[p*2]=(tree[p*2]*lazy2[p])+(lazy[p]*(mid-l+1));
	tree[p*2+1]=(tree[p*2+1]*lazy2[p])+(lazy[p]*(r-(mid+1)+1));
	lazy[p]=0;
	lazy2[p]=1;
}

void add(int p,int l,int r,int x,int y,int num){
	if(x<=l && y>=r){ //找到了对应节点 
		tree2[p]+=(2*tree[p]*num+(r-l+1)*num*num);
		tree[p]+=num*(r-l+1);
		lazy[p]+=num; //加上标记 
		return;
	}
	//if(lazy[p] || lazy2[p]>1){
		pushdown(p,l,r); //p:节点编号,l,r:区间 
	//}
	int mid=(l+r)/2;
	if(x<=mid) add(p*2,l,mid,x,y,num); //左子树与x-y区间有交集 
	if(y>mid) add(p*2+1,mid+1,r,x,y,num); //右子树与x-y区间有交集 
	tree[p]=tree[p*2]+tree[p*2+1]; //y加上z之后,值会发生变化,需要对tree上相应的节点的值重新再算一遍 
	tree2[p]=tree2[p*2]+tree2[p*2+1];
}

void mul(int p,int l,int r,int x,int y,int num){
	if(x<=l && y>=r){ //找到了对应节点 
		tree2[p]=num*num*tree2[p];
		tree[p]=num*tree[p];
		lazy[p]*=num;
		lazy2[p]*=num; //加上标记 
		return;
	}
	//if(lazy[p] || lazy2[p]>1){
		pushdown(p,l,r); //p:节点编号,l,r:区间 
	//}
	int mid=(l+r)/2;
	if(x<=mid) mul(p*2,l,mid,x,y,num); //左子树与x-y区间有交集 
	if(y>mid) mul(p*2+1,mid+1,r,x,y,num); //右子树与x-y区间有交集 
	tree[p]=tree[p*2]+tree[p*2+1]; //y加上z之后,值会发生变化,需要对tree上相应的节点的值重新再算一遍 
	tree2[p]=tree2[p*2]+tree2[p*2+1];
}

int calc(int p,int l,int r,int x,int y){
	if(x<=l && y>=r){ //l-r区间包含在x-y区间内部(完全包含,直接加上这个区间的值即可)
		if(op==1) return tree[p];
		else return tree2[p];
	}
	//if(lazy[p]){
		pushdown(p,l,r);
	//}
	int mid=(l+r)/2;
	int ans=0;
	if(x<=mid) ans+=calc(p*2,l,mid,x,y); 
	if(y>=mid+1) ans+=calc(p*2+1,mid+1,r,x,y);
	return ans;
}

signed main(){
	int n,m;
	scanf("%lld%lld",&n,&m); 
	for(int i=0;i<=N*4-1;i++) lazy2[i]=1;
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
	}
	build(1,1,n); //建树:1:编号为几的节点;1,n:当前编号所代表的左右区间边界 
	while(m--){
		int x,y,z;
		scanf("%lld",&op);
		if(op==1){
			scanf("%lld%lld",&x,&y);
			printf("%lld\n",calc(1,1,n,x,y));
		}else if(op==2){
			scanf("%lld%lld",&x,&y);
			printf("%lld\n",calc(1,1,n,x,y)); 
		}else if(op==3){
			scanf("%lld%lld%lld",&x,&y,&z);
			mul(1,1,n,x,y,z);
		}else{
			scanf("%lld%lld%lld",&x,&y,&z);
			add(1,1,n,x,y,z);
		}
	}
	return 0;
}