2020.01.19日常总结

洛 谷 P 1438        无 聊 的 数 列 \color{green}{洛谷P1438 \ \ \ \ \ \ 无聊的数列} P1438      

【 题 意 】 : \color{blue}{【题意】:}
维护一个数列 a {a} a,支持两种操作:

1、1 L R K D:给出一个长度等于 R − L + 1 R-L+1 RL+1等差数列,首项为 K K K,公差为 D D D,并将它对应加到 a [ L ] − a [ R ] a[L]-a[R] a[L]a[R]的每一个数上。即:令 a [ L ] = a [ L ] + K , a [ L + 1 ] = a [ L + 1 ] + K + D , a [ L + 2 ] = a [ L + 2 ] + K + 2 × D … … a [ R ] = a [ R ] + K + ( R − L ) × D a[L]=a[L]+K,a[L+1]=a[L+1]+K+D,a[L+2]=a[L+2]+K+2\times D……a[R]=a[R]+K+(R-L)\times D a[L]=a[L]+Ka[L+1]=a[L+1]+K+Da[L+2]=a[L+2]+K+2×Da[R]=a[R]+K+(RL)×D

2、2 P:询问序列的第 P P P个数的值 a [ P ] a[P] a[P]

【 思 路 】 : \color{blue}{【思路】:} 我们把 a a a数组进行差分得到 c c c,我们考虑每一次1操作 c c c数组有什么影响。

我们可以发现,每一次的1操作会让c[l]+=f,c[l+1..r]+=d,f[r+1]+=-f-(r-l)*d f f f表示首项, d d d表示公差)。

举个例子:

下标:     1  2...l  l+1   l+2     l+3...     r           r+1      r+2...
a数组增量:+0 +0  +f +f+d +f+2*d  +f+3*d  +f+(r-l)*d       +0       +0
c数组增量:+0 +0  +f  +d    +d      +d        +d       -f-(r-l)*d   +0

从这个表格中我们可以看到一些规律:我们发现直接差分需要将区间 [ l + 1 , r ] [l+1,r] [l+1,r] c c c都增加 d d d,所以我们可以联想到 线 段 树 \color{red}{线段树} 线

讲讲线段树的实现。其实大家对线段树的基本概念都有了解(当默认),重点讲讲标记下传。为了节省时间,我们不能每次都递归到叶子节点计算,所以我们为每一个区间打上一个标记,代表这个区间所有数都加上了这个数。当我们需要遍历到一个节点,准备访问其叶子节点的时候,我们就把标记下放。因为标记下放的时间通常可以不计,默认为 O ( 1 ) O(1) O(1),所以我们就在遍历节点的时候随便把标记下放了,没有增加时间复杂度又保证了正确性。

总的时间复杂度: O ( ( M + N ) × l o g   N ) O((M+N) \times log\ N) O((M+N)×log N),可以通过本题。

【 代 码 】 : \color{blue}{【代码】:}

const int N=1e5+100;
#define ll long long
#define gc getchar()
#define g(c) isdigit(c)
inline ll read(){
	char c=0;ll x=0;bool f=0;
	while (!g(c)) f=c=='-',c=gc;
	while (g(c)) x=x*10+c-48,c=gc;
	return f?-x:x;
}//快读程序
ll sum[N<<2],add[N<<2];
inline void pushup(int o){
	sum[o]=sum[o<<1]+sum[o<<1|1];
}//计算当前节点所代表的区间和
inline void pushdown(int o,int l,int r){
	ll tag=add[o];add[o]=0ll;
	add[o<<1]+=tag;add[o<<1|1]+=tag;
	register int mid=(l+r)>>1;
	sum[o<<1]+=tag*(mid-l+1);
	sum[o<<1|1]+=tag*(r-mid);
}//标记下放,时间复杂度可认为是O(1)
void build(int o,int l,int r){
	sum[o]=add[o]=0ll;
	if (l==r) return;
	register int mid=(l+r)>>1;
	build(o<<1,l,mid);
	build(o<<1|1,mid+1,r);
	pushup(o);return;
}//基于本题的建树操作
void updata(int o,int l,int r,int p,int q,ll v){
	if (l>q||r<p) return;
	if (p<=l&&r<=q){
		sum[o]+=v*(r-l+1);
		add[o]+=v;return;
	}
	if (add[o]) pushdown(o,l,r);
	register int mid=(l+r)>>1;
	updata(o<<1,l,mid,p,q,v);
	updata(o<<1|1,mid+1,r,p,q,v);
	pushup(o);return;
}//区间修改:把区间[p,q]的每一个数都加上v
ll query(int o,int l,int r,int p,int q){
	if (l>q||r<p) return 0ll;
	if (p<=l&&r<=q) return sum[o];
	if (add[o]) pushdown(o,l,r);
	register int mid=(l+r)>>1;
	register ll ans=0ll;
	ans+=query(o<<1,l,mid,p,q);
	ans+=query(o<<1|1,mid+1,r,p,q);
	return ans;
}//询问操作:询问区间[p,q]的总和
int n,m;ll a[N];
int main(){
	n=read();m=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	build(1,1,n);
	for(int i=1;i<=m;i++){
		ll opt=read(),l=read();
		if (opt==1){
			ll r=read(),f=read(),d=read();
			if (l==r){
				updata(1,1,n,l,l,f);
				if (l!=n) updata(1,1,n,l+1,l+1,-f);
			}
			else{
				updata(1,1,n,l,l,f);updata(1,1,n,l+1,r,d);
				if (r!=n) updata(1,1,n,r+1,r+1,-f-(r-l)*d);
			}
		}
		else printf("%lld\n",query(1,1,n,1,l)+a[l]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值