20200428 T1 Arithmetic【Δ线段树(每个位置加的值不一样的线段树)】

题目描述:

在这里插入图片描述

题目分析:

(标题取成这样因为实在不知道叫什么好。。)
在这里插入图片描述

不难发现我们更新线段树时需要根据每个左端点对应的Max和Min进行加减,而不是像普通的线段树那样区间整体加上一个相同的值。
考虑我们的线段树需要支持什么操作,记每个位置的变化量为 Δ \Delta Δ(即当区间整体+1时这个位置需要加的量):

  • 给区间 [ l , r ] [l,r] [l,r]每个位置的 Δ \Delta Δ加上一个值 x x x (可正可负)
  • 令区间 [ l , r ] [l,r] [l,r]每个位置的答案加上其对应的 Δ ∗ k \Delta*k Δk,(此题中 k = 1 k=1 k=1 [ l , r ] [l,r] [l,r]可以只用 [ 1 , n ] [1,n] [1,n]
  • 求区间 [ l , r ] [l,r] [l,r]的答案的和

为此,我们的线段树需要记录这几个量:

  • s u m i sum_i sumi :表示该区间答案的和。
  • s d i sd_i sdi:表示该区间 Δ \Delta Δ的和。
  • k i k_i ki:表示答案整体加 Δ \Delta Δ次数的懒标记。
  • t a g i tag_i tagi:表示修改 Δ \Delta Δ的懒标记。
  • a d d i add_i addi:(这是一个比较特别的标记,不同的人可能有不同的写法,在后面介绍)
  • l e n i len_i leni:该区间的长度,方便pushdown。

p u s h u p pushup pushup就这样写:

inline void upd(int i){
	t[i].sum=t[lc].sum+t[rc].sum;
	t[i].sd=t[lc].sd+t[rc].sd;
}

问题主要在于 p u s h d o w n pushdown pushdown,我们需要考虑标记优先级的问题,这里假设定义 k k k的优先级高于 t a g tag tag(即先加答案,再修改 Δ \Delta Δ)。

先忽略 a d d add add标记,考虑标记 ( k i , t a g i ) (k_i,tag_i) (ki,tagi)怎么传给 x x x,首先肯定有:
s u m x + = k i ∗ s d x sum_x+=k_i*sd_x sumx+=kisdx
k x + = k i k_x+=k_i kx+=ki
t a g x + = t a g i tag_x+=tag_i tagx+=tagi
s d x + = t a g i ∗ l e n x sd_x+=tag_i*len_x sdx+=tagilenx
那么这时候问题就来了!对于 x x x的儿子 y y y,当 x x x p u s h d o w n pushdown pushdown的时候,它本来应该先答案加上 k x ∗ s d y k_x*sd_y kxsdy,再修改 s d y sd_y sdy,然后当 i i i下传给 x x x再下传给 y y y的时候,给答案加上 k i ∗ s d y k_i*sd_y kisdy,再修改 s d y sd_y sdy,可以现在 i i i提前传给了 x x x,这就导致了传给 y y y的时候, k i k_i ki本来应该是在 s d y sd_y sdy修改之后再加的,但是由于 k k k的优先级高于 t a g tag tag,所以 s u m y sum_y sumy就加少了!少了多少呢?就是少加给 Δ \Delta Δ k i ∗ t a g x k_i*tag_x kitagx!这就是 a d d add add所记录的信息。(此处可以稍作思考)

a d d i add_i addi记录的是当标记 ( k r t , t a g r t ) (k_{rt},tag_{rt}) (krt,tagrt)下传给 i i i时,将会少加给 i i i的儿子的 s u m sum sum的关于每个位置的 Δ \Delta Δ的信息,即 k r t ∗ t a g i k_{rt}*tag_i krttagi(说的不是特别清楚,结合代码理解一下)

重新梳理一下 p u s h d o w n pushdown pushdown应该怎么写:

inline void pd(int i,int x){
	t[x].sum+=t[i].k*t[x].sd+t[i].add*t[x].len;//add记录的是每个位置对答案的额外贡献
	t[x].add+=t[i].k*t[x].tag+t[i].add;
	t[x].k+=t[i].k;
	t[x].tag+=t[i].tag;
	t[x].sd+=t[i].tag*t[x].len;
}

另外,对于题目中的 − ( r − l ) -(r-l) (rl),其实就是对每个 r r r [ L r , r − 1 ] [L_r,r-1] [Lr,r1] Δ − 1 \Delta-1 Δ1,然后令 [ L r , r − 1 ] [L_r,r-1] [Lr,r1] k + 1 k+1 k+1

Code:

#include<bits/stdc++.h>
#define maxn 300005
#define LL long long
using namespace std;
char cb[1<<20],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<20,stdin),cs==ct)?0:*cs++)
template<class T>inline void read(T &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
int n,d,m,a[maxn],A[maxn],An,B[maxn],Bn,last[10000005];
LL ans[maxn];
struct node{
	int l,r,id; bool operator < (const node &p)const{return r<p.r;}
}Q[maxn];
struct Seg{
	int len;
	LL sum,sd,tag,k,add;
	//sd: sum of delta, tag: whole delta of delta, k: times of delta affected. add: when k+=1, additional tag left for sum.
}t[maxn<<2];
#define lc (i<<1)
#define rc (i<<1|1)
inline void upd(int i){
	t[i].sum=t[lc].sum+t[rc].sum;
	t[i].sd=t[lc].sd+t[rc].sd;
}
inline void pd(int i,int x){
	t[x].sum+=t[i].k*t[x].sd+t[i].add*t[x].len;
	t[x].add+=t[i].k*t[x].tag+t[i].add;
	t[x].k+=t[i].k;
	t[x].tag+=t[i].tag;
	t[x].sd+=t[i].tag*t[x].len;
}
inline void down(int i){pd(i,lc),pd(i,rc),t[i].tag=t[i].k=t[i].add=0;}
void build(int i,int l,int r){
	t[i].len=r-l+1;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(lc,l,mid),build(rc,mid+1,r);
}
void mdf(int i,int l,int r,int x,int y,LL d){
	if(x<=l&&r<=y) {t[i].sd+=d*t[i].len,t[i].tag+=d;return;}
	down(i);
	int mid=(l+r)>>1;
	if(x<=mid) mdf(lc,l,mid,x,y,d);
	if(y>mid) mdf(rc,mid+1,r,x,y,d);
	upd(i);
}
LL qry(int i,int l,int r,int x,int y){
	if(x<=l&&r<=y) return t[i].sum;
	down(i);
	int mid=(l+r)>>1; LL ret=0;
	if(x<=mid) ret+=qry(lc,l,mid,x,y);
	if(y>mid) ret+=qry(rc,mid+1,r,x,y);
	return ret;
}
void zero(int i,int l,int r,int x){
	if(l==r) return void(t[i].sd=0);
	down(i);
	int mid=(l+r)>>1;
	x<=mid?zero(lc,l,mid,x):zero(rc,mid+1,r,x);
	upd(i);
}
inline void accumulate(){
	t[1].sum+=t[1].sd,t[1].add+=t[1].tag,t[1].k++;
}
int main()
{
	freopen("arithmetic.in","r",stdin);
	freopen("arithmetic.out","w",stdout);
	read(n),read(d),read(m);
	if(!n||!m) return 0;
	for(int i=1;i<=n;i++) read(a[i]);
	for(int i=1;i<=m;i++) read(Q[i].l),read(Q[i].r),Q[i].id=i;
	sort(Q+1,Q+1+m);
	int L=1; build(1,1,n);
	for(int i=1,j=1;i<=n;i++){
		int pre=L;
		if(a[i]%d==a[i-1]%d) L=max(L,last[a[i]]+1); else L=i;
		last[a[i]]=i;
		while(pre<L) zero(1,1,n,pre++);//let delta be 0.
		
		while(A[An]>=L&&a[i]>a[A[An]]) mdf(1,1,n,max(A[An-1]+1,L),A[An],-a[A[An]]/d),--An;
		mdf(1,1,n,max(A[An]+1,L),i,a[i]/d),A[++An]=i;//max/d
	
		while(B[Bn]>=L&&a[i]<a[B[Bn]]) mdf(1,1,n,max(B[Bn-1]+1,L),B[Bn],a[B[Bn]]/d),--Bn;
		mdf(1,1,n,max(B[Bn]+1,L),i,-a[i]/d),B[++Bn]=i;//-min/d
		
		if(L<i) mdf(1,1,n,L,i-1,-1);//-(r-l)
		accumulate();
		
		for(;j<=m&&Q[j].r<=i;j++) ans[Q[j].id]=qry(1,1,n,Q[j].l,i);
	}
	for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值