徐州网络赛 - H
Ryuji doesn’t want to study
Ryuji is not a good student, and he doesn’t want to study. But there are n books he should learn, each book has its knowledge a[i].
Unfortunately, the longer he learns, the fewer he gets.
That means, if he reads books from ll to rr, he will get a[l]×L+a[l+1]×(L−1)+⋯+a[r−1]×2+a[r] (L is the length of [l,r] that equals to r-l+1).
Now Ryuji has qq questions, you should answer him:
-
If the question type is 1, you should answer how much knowledge he will get after he reads books [l,r].
-
If the question type is 2, Ryuji will change the ith book’s knowledge to a new value.
Input
First line contains two integers nn and qq (n,q≤100000).
The next line contains n integers represent ai(ai≤1e9) .
Then in next q line each line contains three integers a, b, c, if a=1, it means question type is 1, and b, c represents [l,r]. if a=2 , it means question type is 2 , and b, c means Ryuji changes the bth book’ knowledge to c
Output
For each question, output one line with one integer represent the answer.
样例输入
5 3
1 2 3 4 5
1 1 3
2 5 0
1 4 5
样例输出
10
8
分析:
题意就很像是求线段树的题目,有两个操作,操作1是单点修改,操作2是求一段区间的某种和,这个和是al * len(区间长度) + a(l+1) * (len-1)… + ar * 1,下文就简称为乘积和。其实对于这个操作我们可以在计算时这样考虑,首先用线段树保存两个东西,一个是区间和,另一个就是区间与区间长度乘积和(就是题目要求的那个和),我们用两个小区间来求大区间的时候就可以用左区间的乘积和 + 右区间的乘积和 + 左区间的区间和 * 右区间的长度。最后,因为查询的时候需要乘积和和区间和,我就存了一个结构体来同时返回两个东西。
举个例子:
1 2 3 4 5
假设左区间为[1,3],区间和就是1 + 2 + 3=6,区间乘积和就是1 * 3 + 2 * 2 + 3 * 1 = 10
右区间为[4,5],区间和就是4+5=9,区间乘积和就是4 * 2 + 5 * 1 = 13
整个区间的乘积和就是:
1 * 5 + 2 * 4 + 3 * 3 + 4 * 2 + 5 * 1 = 35
10 + 13 + 6 * 2 = 35
算出来的答案是一样的。
实际上我们直接相加乘积和就是
1 * 3 + 2 * 2 + 3 * 1 + 4 * 2 + 5 * 1
后半部分是完全一样的,前半部分少的就是右半部分的长度个前半部分区间和。
这个其实稍微想一下就可以想出来。
参考代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#define MAXN 100005
#define left rt<<1
#define right rt<<1|1
typedef long long ll;
using namespace std;
int n,q,op,l,r,p;
ll c;
struct SegTree {
int l,r;
// sum为区间和,sum1为乘积和
ll sum,sum1,len;
}t[MAXN<<2];
struct node {
ll sum,sum1;
node(){sum=0; sum1=0;}
}ans;
void pushup(int rt) {
t[rt].sum=t[left].sum+t[right].sum;
// 这一部分就是乘积和的pushup
t[rt].sum1=t[left].sum1+t[right].len*t[left].sum+t[right].sum1;
}
// 建树和更新都是正常的
void build(int l,int r,int rt) {
t[rt].l=l; t[rt].r=r; t[rt].len=r-l+1;
if (l==r) {
scanf("%lld",&t[rt].sum);
t[rt].sum1=t[rt].sum;
return;
}
int m=(l+r)>>1;
build(l,m,left);
build(m+1,r,right);
pushup(rt);
}
void update(int p,ll c,int rt) {
if (t[rt].l==t[rt].r) {
t[rt].sum=t[rt].sum1=c;
return;
}
int m=(t[rt].l+t[rt].r)>>1;
if (p<=m) update(p,c,left);
if (p>m) update(p,c,right);
pushup(rt);
}
// 返回的是结构体
node query(int L,int R,int rt) {
node tmp,tmpl,tmpr;
if (L<=t[rt].l && t[rt].r<=R) {
tmp.sum=t[rt].sum; tmp.sum1=t[rt].sum1;
return tmp;
}
int m=(t[rt].l+t[rt].r)>>1;
if (L<=m) tmpl=query(L,R,left);
if (R>m) tmpr=query(L,R,right);
tmp.sum=tmpl.sum+tmpr.sum;
tmp.sum1=tmpl.sum1+tmpr.sum1;
// 需要注意的是右半部分区间长度的计算,一开始错在这里了
// 右端点应该是当前区间最右端和查询区间最右端的最小值,具体可以举个例子画一下
if (R>m) tmp.sum1=tmp.sum1+(min(R,t[rt].r)-m)*tmpl.sum;
return tmp;
}
int main() {
scanf("%d%d",&n,&q);
build(1,n,1);
for (int i=1;i<=q;i++) {
scanf("%d",&op);
if (op==2) {
scanf("%d%lld",&p,&c);
update(p,c,1);
} else {
scanf("%d%d",&l,&r);
ans=query(l,r,1);
printf("%lld\n",ans.sum1);
}
}
return 0;
}