Codeforces 316E3 Summer Homework 线段树+前缀和

题目链接:传送门

题外话:这是一个蒟蒻在看不懂所有题解时,受到巨神mhy的帮助后写的题解。

我太蒻了qwq

题目描述

By the age of three Smart Beaver mastered all arithmetic operations and I love zwt got this summer homework from the amazed teacher:

You are given a sequence of integers a 1 ,   a 2 ,   . . . ,   a n a_1, a_2, ..., a_n a1,a2,...,an. Your task is to perform on it m consecutive operations of the following type:

For given numbers x i x_i xi and v i v_i vi assign value v i v_i vi to element a x i a_{x_i} axi.
For given numbers l i l_i li and r i r_i ri you’ve got to calculate sum ∑ x = 0 r i − l i f x ⋅ a l i + x \sum_{x=0}^{r_i-l_i}{f_x·a_{l_i+x}} x=0rilifxali+x, where f 0 =   f 1   =   1 f_0 = f_1 = 1 f0=f1=1 and at i   ≥   2 : f i   =   f i − 1   +   f i − 2 i ≥ 2: f_i = f_{i-1} + f_{i-2} i2:fi=fi1+fi2.
For a group of three numbers l i , r i , d i l_i,r_i,d_i li,ri,di you should increase value a x a_x ax by d i d_i di for all x ( l i   ≤   x   ≤   r i ) x (l_i ≤ x ≤ r_i) x(lixri).
Smart Beaver planned a tour around great Canadian lakes, so he asked you to help him solve the given problem.

Input
The first line contains two integers n and m ( 1   ≤   n ,   m   ≤   2 ⋅ 1 0 5 ) (1 ≤ n, m ≤ 2·10^5) (1n,m2105) — the number of integers in the sequence and the number of operations, correspondingly. The second line contains n n n integers a 1 ,   a 2 ,   . . . ,   a n ( 0   ≤   a i   ≤   1 0 5 ) a_1, a_2, ..., a_n (0 ≤ a_i ≤ 10^5) a1,a2,...,an(0ai105). Then follow m m m lines, each describes an operation. Each line starts with an integer t i t_i ti ( 1   ≤   t i   ≤   3 ) (1 ≤ t_i ≤ 3) (1ti3) — the operation type:

if t i   =   1 t_i = 1 ti=1, then next follow two integers x i , v i ( 1   ≤   x i   ≤   n ,   0   ≤   v i   ≤   1 0 5 ) x_i,v_i (1 ≤ x_i ≤ n, 0 ≤ v_i ≤ 10^5) xi,vi(1xin,0vi105);
if t i   =   2 t_i = 2 ti=2, then next follow two integers l i , r i ( 1   ≤   l i   ≤   r i   ≤   n ) l_i,r_i (1 ≤ l_i ≤ r_i ≤ n) li,ri(1lirin);
if t i   =   3 t_i = 3 ti=3, then next follow three integers l i , r i , d i ( 1   ≤   l i   ≤   r i   ≤   n ,   0   ≤   d i   ≤   1 0 5 ) l_i,r_i,d_i (1 ≤ l_i ≤ r_i ≤ n, 0 ≤ d_i ≤ 10^5) li,ri,di(1lirin,0di105).
The input limits for scoring 30 points are (subproblem E1):

It is guaranteed that n does not exceed 100, m does not exceed 10000 and there will be no queries of the 3-rd type.
The input limits for scoring 70 points are (subproblems E1+E2):

It is guaranteed that there will be queries of the 1-st and 2-nd type only.
The input limits for scoring 100 points are (subproblems E1+E2+E3):

No extra limitations.
Output
For each query print the calculated sum modulo 1000000000 ( 1 0 9 10^9 109).

题意解释

给出n个数,m个询问,
要求:
1.支持区间修改和单点修改。
2.支持询问 ∑ x = 0 r i − l i f x ⋅ a l i + x \sum_{x=0}^{r_i-l_i}{f_x·a_{l_i+x}} x=0rilifxali+x,其中 f x f_x fx表示第x个斐波那契数。

大致思路

注:题面中的 f f f数组下标是从0开始的,此篇题解的下标从1开始,因此有些地方与题面不一样。
首先观察数据范围: n &lt; = 2 ⋅ 1 0 5 n&lt;=2·10^5 n<=2105,又要支持单点修改和区间修改,想到用线段树。
此题询问的信息比较特殊,考虑引进S:
在每个线段树节点中,令 [ l , r ] [l,r] [l,r]表示此节点的区间。
令:
S 1 = f 1 a l + f 2 a l + 1 + f 3 a l + 2 + . . . + f r − l + 1 a r S_1=f_1a_l+f_2a_{l+1}+f_3a_{l+2}+...+f_{r-l+1}a_r S1=f1al+f2al+1+f3al+2+...+frl+1ar
S 2 = f 2 a l + f 3 a l + 1 + f 4 a l + 2 + . . . + f r − l + 2 a r S_2=f_2a_l+f_3a_{l+1}+f_4a_{l+2}+...+f_{r-l+2}a_r S2=f2al+f3al+1+f4al+2+...+frl+2ar
S 3 = f 3 a l + f 4 a l + 1 + f 5 a l + 2 + . . . + f r − l + 3 a r S_3=f_3a_l+f_4a_{l+1}+f_5a_{l+2}+...+f_{r-l+3}a_r S3=f3al+f4al+1+f5al+2+...+frl+3ar
. . . . . . ...... ......
S x = f x a l + f x + 1 a l + 1 + f x + 2 a l + 2 + . . . + f r − l + x a r S_x=f_xa_l+f_{x+1}a_{l+1}+f_{x+2}a_{l+2}+...+f_{r-l+x}a_r Sx=fxal+fx+1al+1+fx+2al+2+...+frl+xar

找规律,发现:
S 1 + S 2 S_1+S_2 S1+S2
= ( f 1 + f 2 ) a l + ( f 2 + f 3 ) a l + 1 + ( f 3 + f 4 ) a l + 2 + . . . + ( f r − l + 1 + f r − l + 2 ) a r =(f_1+f_2)a_l+(f_2+f_3)a_{l+1}+(f_3+f_4)a_{l+2}+...+(f_{r-l+1}+f_{r-l+2})a_r =(f1+f2)al+(f2+f3)al+1+(f3+f4)al+2+...+(frl+1+frl+2)ar
= f 3 a l + f 4 a l + 1 + f 5 a l + 2 + . . . + f r − l + 3 a r =f_3a_l+f_4a_{l+1}+f_5a_{l+2}+...+f_{r-l+3}a_r =f3al+f4al+1+f5al+2+...+frl+3ar
= S 3 =S_3 =S3

如果再推下去,则会发现 S 2 + S 3 = S 4 S_2+S_3=S_4 S2+S3=S4 S 3 + S 4 = S 5 . . . . . . . . S_3+S_4=S_5........ S3+S4=S5........

因此,可以证明 S x = S x − 1 + S x − 2 S_x=S_{x-1}+S_{x-2} Sx=Sx1+Sx2

再把式中的 S x − 1 , S x − 2 S_{x-1},S_{x-2} Sx1Sx2分解下去,得 S x = . . . = f x − 2 S 1 + f x − 1 S 2 S_x=...=f_{x-2}S_1+f_{x-1}S_2 Sx=...=fx2S1+fx1S2

因此,只要确定了某个节点的 S 1 , S 2 S_1,S_2 S1,S2,就能通过预处理好的 f f f数组来快速得到 S x S_x Sx的值

所以,线段树中维护 S 1 , S 2 S_1,S_2 S1,S2两个值,用 l a z y t a g lazytag lazytag来维护区间加标记。
接下来是具体操作:

如何得到答案(即Query函数)

假设需要查询的区间 [ l , r ] [l,r] [l,r]是黑色部分,当前线段树节点的区间 [ L , R ] [L,R] [L,R]是红色部分:

需要求的值是黑色字体部分,线段树中维护的值是红色字体部分:

那么,如何用线段树维护的 S 1 , S 2 S_1,S_2 S1,S2信息得到上面一大坨黑色字体的结果呢?
由于线段树Query是递归进行的,因此在当前线段树区间是 [ L , R ] [L,R] [L,R]时,需要求得的值是:
f L − l + 1 a L + . . . + f R − l + 1 a R f_{L-l+1}a_L+...+f_{R-l+1}a_R fLl+1aL+...+fRl+1aR
观察发现,这个值就是当前区间的 S L − l + 1 S_{L-l+1} SLl+1
又因为上面已经证明过 S x = f x − 2 S 1 + f x − 1 S 2 S_x=f_{x-2}S_1+f_{x-1}S_2 Sx=fx2S1+fx1S2
所以预处理出 f f f数组,然后大力 O ( 1 ) O(1) O(1)算出此时的 S L − l + 1 S_{L-l+1} SLl+1即可。

区间修改(即Update函数)

单点修改很简单,这里就不讲了。。
回到图片,假设当前线段树区间为 [ L , R ] [L,R] [L,R],区间加 v v v,用 S 1 S_1 S1举例:
(图中打错了,应该是 S 1 S_1 S1而不是 S 0 S_0 S0

计算差值,发现 S 0 S_0 S0增加的值为 F 1 ⋅ v + F 2 ⋅ v + . . . + F R − L + 1 ⋅ v F_1·v+F_2·v+...+F_{R-L+1}·v F1v+F2v+...+FRL+1v
因此可以处理出 f f f数组的前缀和,然后大力算出 S 0 S_0 S0增加的值即可。
S 1 S_1 S1的维护同理。

维护信息(即Pushup函数)

S 1 S_1 S1为例。
P u s h u p Pushup Pushup如下 不画图了,偷一波懒

tree[rt].s1=(tree[lc].s1+S(rc,mid-tree[rt].l+2))%mod;

其中tree[rt]表示当前的线段树节点,lc表示左孩子的下标,rc表示右孩子的下标。
如何证明这样维护是正确的呢?
这里分别展开等式的左右两端:
设当前区间为 [ l , r ] [l,r] [l,r]
左边=
F 1 a l + F 2 a l + 1 + . . . + F r − l + 1 a r F_1a_l+F_2a_{l+1}+...+F_{r-l+1}a_r F1al+F2al+1+...+Frl+1ar
令mid为当前区间的中点,即 m i d = ( l + r ) / 2 mid=(l+r)/2 mid=(l+r)/2
右边=
( F 1 a l + F 2 a l + 1 + . . . + F m i d − l + 1 a m i d ) + S m i d − l + 2 (F_1a_l+F_2a_{l+1}+...+F_{mid-l+1}a_{mid})+S_{mid-l+2} (F1al+F2al+1+...+Fmidl+1amid)+Smidl+2
= ( F 1 a l + F 2 a l + 1 + . . . + F m i d − l + 1 a m i d ) + ( F m i d − l + 2 a m i d + 1 + . . . + F r − l + 1 a r ) =(F_1a_l+F_2a_{l+1}+...+F_{mid-l+1}a_{mid})+(F_{mid-l+2}a_{mid+1}+...+F_{r-l+1}a_r) =(F1al+F2al+1+...+Fmidl+1amid)+(Fmidl+2amid+1+...+Frl+1ar)
= F 1 a l + F 2 a l + 1 + . . . + F r − l + 1 a r =F_1a_l+F_2a_{l+1}+...+F_{r-l+1}a_r =F1al+F2al+1+...+Frl+1ar
= = =左边
因此,这样维护是正确的qwq。

下推标记(即Pushdown函数)

类比Update即可。

代码

请原谅我的毒瘤码风qwq

#include<stdio.h>
#include<cstring>
#include<algorithm>
#define re register int
#define lc rt<<1
#define rc rt<<1|1
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=200005;
const ll mod=1e9;
int n,m,a[Size];
ll f[Size],sumf[Size];
ll f2[Size],sumf2[Size];
struct node {
	int l,r;
	ll s1,s2;
	ll lazy;
	inline int len() {
		return r-l+1;
	}
} tree[Size<<2];
inline ll S(int rt,int x) {
	if(x==1)	return tree[rt].s1;
	if(x==2)	return tree[rt].s2;
	return (f[x-2]*tree[rt].s1%mod+f[x-1]*tree[rt].s2%mod)%mod;
}
inline void Pushup(int rt) {
	int mid=(tree[rt].l+tree[rt].r)>>1;
	tree[rt].s1=(tree[lc].s1+S(rc,mid-tree[rt].l+2))%mod;
	tree[rt].s2=(tree[lc].s2+S(rc,mid-tree[rt].l+3))%mod;
}
void Build(int l,int r,int rt) {
	tree[rt].l=l;
	tree[rt].r=r;
	if(l==r) {
		tree[rt].s1=tree[rt].s2=a[l];
		return;
	}
	int mid=(l+r)>>1;
	Build(l,mid,lc);
	Build(mid+1,r,rc);
	Pushup(rt);
}
void Pushdown(int rt) {
	if(tree[rt].lazy) {
		int lenl=tree[lc].len(),lenr=tree[rc].len();
		tree[lc].s1=(tree[lc].s1+sumf[lenl]*tree[rt].lazy)%mod;
		tree[lc].s2=(tree[lc].s2+sumf2[lenl]*tree[rt].lazy)%mod;
		tree[rc].s1=(tree[rc].s1+sumf[lenr]*tree[rt].lazy)%mod;
		tree[rc].s2=(tree[rc].s2+sumf2[lenr]*tree[rt].lazy)%mod;
		tree[lc].lazy+=tree[rt].lazy;
		tree[rc].lazy+=tree[rt].lazy;
		tree[rt].lazy=0;
	}
}
void UpdateNode(int x,int v,int rt) {
	if(tree[rt].l==tree[rt].r) {
		tree[rt].s1=tree[rt].s2=v;
		return;
	}
	Pushdown(rt);
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(x<=mid)	UpdateNode(x,v,lc);
	if(x>mid)	UpdateNode(x,v,rc);
	Pushup(rt);
}
void Update(int l,int r,ll v,int rt) {
	if(l<=tree[rt].l && tree[rt].r<=r) {
		int len=tree[rt].len();
		tree[rt].s1=(tree[rt].s1+sumf[len]*v)%mod;
		tree[rt].s2=(tree[rt].s2+sumf2[len]*v)%mod;
		tree[rt].lazy+=v;
		return;
	}
	Pushdown(rt);
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(l<=mid)	Update(l,r,v,lc);
	if(r>mid)	Update(l,r,v,rc);
	Pushup(rt);
}
ll Query(int l,int r,int rt) {
	if(l<=tree[rt].l && tree[rt].r<=r) {
		return S(rt,tree[rt].l-l+1);
	}
	Pushdown(rt);
	int mid=(tree[rt].l+tree[rt].r)>>1;
	ll ans=0;
	if(l<=mid)	ans=(ans+Query(l,r,lc))%mod;
	if(r>mid)	ans=(ans+Query(l,r,rc))%mod;
	return ans;
}
void GetFib() {
	f[1]=f[2]=1;
	sumf[1]=1;
	sumf[2]=2;
	f2[1]=1; f2[2]=2;
	sumf2[1]=1;
	sumf2[2]=3;
	for(re i=3; i<=n; i++) {
		f[i]=(f[i-1]+f[i-2])%mod;
		sumf[i]=(sumf[i-1]+f[i])%mod;
		f2[i]=(f2[i-1]+f2[i-2])%mod;
		sumf2[i]=(sumf2[i-1]+f2[i])%mod;
	}
}
int main() {
	n=read();
	m=read();
	for(re i=1; i<=n; i++)	a[i]=read();
	GetFib();
	Build(1,n,1);
	int x,v,l,r;
	while(m--) {
		int t=read();
		if(t==1) {
			x=read();
			v=read();
			UpdateNode(x,v,1);
		} else if(t==2) {
			l=read();
			r=read();
			printf("%lld\n",Query(l,r,1));
		} else {
			l=read();
			r=read();
			v=read();
			Update(l,r,v,1);
		}
	}
    return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值