Arithmetic(线段树维护历史版本和)

题意:一个多重集合的价值为将其变为公差为 d d d(将在第一行输入)的等差数列需要插入几个数字(无法实现价值为0)。多次询问 l , r l,r l,r,求 [ l , r ] [l,r] [l,r]的子区间的价值和。
序列长度 3 e 5 3e5 3e5,数字大小和 d ≤ 1 e 7 d\leq 1e7 d1e7

首先对于 r ′ > r r'>r r>r l l l使得 [ l , r ] [l,r] [l,r]是最长的可以实现的区间, l ′ l' l是使得 [ l ′ , r ′ ] [l',r'] [l,r]是最长的可以实现的区间,那么 l ′ ≥ l l' \geq l ll,所以我们可以用双指针扫一遍求出所有可以实现的区间在哪些区间内。
那么对于所有可以实现的区间,我们有一个简单的方法可以计算一个区间 [ l , r ] [l,r] [l,r]构成的集合的价值:
最 大 值 d − 最 小 值 d + l − r \frac {最大值}d - \frac {最小值}d + l - r dd+lr
然后就是很套路的在双指针的同时维护两个单调栈,在栈中发生插入和删除操作的同时在线段树上区间加减。
那么如果我们问的是 [ l , r ] [l,r] [l,r]的价值,离线后就是双指针右端点到达 r r r时,单点查询 l l l.
如果问的是 ∑ i = l r [ i , r ] \sum_{i=l}^r [i,r] i=lr[i,r]的价值,那么就是区间查询 l , r l,r l,r
可是我们询问的是 ∑ i = l r ∑ j = i r [ i , j ] \sum_{i=l}^r \sum_{j=i}^r [i,j] i=lrj=ir[i,j]的价值和。
这就意味着我们在右端点是 r r r的时候还需要统计到右端点是 r − 1... r-1... r1...的答案。
所以我们假设右端点在 i i i的时候就是我们线段树的第 i i i个版本。
那么我们相当于就是要求线段树的历史版本区间和 h s u m hsum hsum
需要增加一个标记 t a g tag tag,这个标记的意义是:
当一个区间打上 t a g + = v tag += v tag+=v时, h s u m + = s u m ∗ v hsum += sum * v hsum+=sumv。然后在之后下放给儿子。
考虑这个标记会和区间加 a d d add add标记有不良互动。于是我们让一个区间在同时有 a d d add add t a g tag tag时先下放 a d d add add再下放 t a g tag tag
那么在一个有 a d d add add的点上打 t a g tag tag不会影响先后顺序,没有问题。但是反过来在有 t a g tag tag的点上打 a d d add add后下放这个点的标记就会造成前后颠倒。
那么就把多加的 h s u m hsum hsum减回去,新加一个标记 a d d h addh addh,当一个区间打上 a d d h + = v addh+= v addh+=v时, h s u m + = l e n ∗ v hsum += len * v hsum+=lenv。然后在之后下放给儿子,这里的len是这个区间的长度。
那么就结束了。

A C   C o d e \rm AC \ Code AC Code

#include
#define maxn 300005
#define F first
#define S second
#define mp make_pair
#define pii pair
#define LL long long
#define lc u<<1
#define rc lc|1
using namespace std;

int n,d,Q,S[maxn];
vector<pii >G[maxn];

LL sm[maxn<<3],hsm[maxn<<3],ad[maxn<<3],adh[maxn<<3],len[maxn<<3],had[maxn<<3];
void dtp2(int u,int v){ had[u] += v , hsm[u] += sm[u] * v; }
void dtp3(int u,LL v,int t=1){ adh[u] += v; if(t) hsm[u] += v * len[u]; }
void dtp1(int u,int v){ 
	if(had[u]) dtp3(u,-1ll*had[u]*v,0);
	ad[u] += v , sm[u] += 1ll * len[u] * v;  
}
void dt(int u){ 
	if(ad[u]) dtp1(lc,ad[u]),dtp1(rc,ad[u]),ad[u]=0; 
	if(adh[u]) dtp3(lc,adh[u]),dtp3(rc,adh[u]),adh[u]=0;
	if(had[u]) dtp2(lc,had[u]),dtp2(rc,had[u]),had[u]=0;
}
void add(int u,int l,int r,int ql,int qr,int v){
	if(l>qr||ql>r) return;
	if(ql<=l&&r<=qr) return (void)(dtp1(u,v));
	int m=l+r>>1;dt(u);
	add(lc,l,m,ql,qr,v),add(rc,m+1,r,ql,qr,v);
	sm[u] = sm[lc] + sm[rc];
}
void pud(int u,int l,int r,int ql,int qr){
	if(l>qr||ql>r) return;
	if(ql<=l&&r<=qr) return (void)(dtp2(u,1));
	int m=l+r>>1;dt(u);
	pud(lc,l,m,ql,qr),pud(rc,m+1,r,ql,qr);
	hsm[u] = hsm[lc] + hsm[rc];
}
LL qry(int u,int l,int r,int ql,int qr){
	if(l>qr||ql>r) return 0;
	if(ql<=l&&r<=qr) return hsm[u];
	int m=l+r>>1;dt(u);
	return qry(lc,l,m,ql,qr) + qry(rc,m+1,r,ql,qr);
}
void Build(int u,int l,int r){
	if(l>r) return;
	len[u] = r-l+1;
	if(l==r) return;
	int m=l+r>>1;
	Build(lc,l,m),Build(rc,m+1,r);
}
int st[2][maxn],qr[2];
int cd[10000007],scd,usd[10000007],sud;
LL ans[maxn];

int main(){
	
	freopen("arithmetic.in","r",stdin);
	freopen("arithmetic.out","w",stdout);
	
	scanf("%d%d%d",&n,&d,&Q);
	for(int i=1;i<=n;i++) scanf("%d",&S[i]);

	for(int i=1,a,b;i<=Q;i++){
		scanf("%d%d",&a,&b);
		G[b].push_back(mp(a,i));
	}
	Build(1,1,n);
	for(int i=1,j=1;i<=n;i++){
		for(;qr[0] && S[st[0][qr[0]]] >= S[i];qr[0]--)
			add(1,1,n,st[0][qr[0]-1]+1,st[0][qr[0]],(S[st[0][qr[0]]]/d));
		st[0][++qr[0]] = i;
		add(1,1,n,st[0][qr[0]-1]+1,i,-(S[i]/d));
		for(;qr[1] && S[st[1][qr[1]]] <= S[i];qr[1]--)
			add(1,1,n,st[1][qr[1]-1]+1,st[1][qr[1]],-(S[st[1][qr[1]]]/d));
		st[1][++qr[1]] = i;
		add(1,1,n,st[1][qr[1]-1]+1,i,S[i]/d);
		if(i>1) add(1,1,n,1,i-1,-1);
		scd += (cd[S[i] % d] ++ == 0);
		sud += (++usd[S[i]] == 2);
		for(;j<=n && (scd > 1 || sud >= 1);j++)
			scd -= (--cd[S[j] % d]  == 0),
			sud -= (usd[S[j]]-- == 2);
		
		pud(1,1,n,j,i);
		for(int k=0;k<G[i].size();k++)
			ans[G[i][k].S] = qry(1,1,n,G[i][k].F,i);
	} 
	for(int i=1;i<=Q;i++) printf("%lld\n",ans[i]);
}

你可能感兴趣的:(数据结构,序列)