1. 程式人生 > >[HZOI 2016]數列操作d

[HZOI 2016]數列操作d

roo pen font HA 線段樹 高速公路 思想 標記 區間

我的第一道題解就寫它吧。

維護區間和+花式修改。用線段樹或者樹狀數組可以解決,但是我沒怎麽寫過樹狀數組。

維護和的操作直接把左右子樹和加起來。

重點是修改。

去年剛學完線段樹刷完數列操作a,b,c後看道這題就棄了。現在知道了關鍵就是推式子,跟HAOI2012高速公路是一個套路的。

線段樹就是用於維護區間的,而且因為延遲標記的存在,所以我們先考慮區間增量。

對於區間 [l,r] 增量為 Σri=l(i-L)*x。L是總的修改範圍,l,r,是線段樹中節點的範圍,第一次就是因為沒註意這兩者的關系,導致公式錯誤連樣例都過不了。

提公因式x,Σ裏是等差數列直接求和之後相乘就算出增量了,這時候要考慮如何打lazy標記。

思想也是提公因式。

Σri=l ( i-L ) * x = ( Σri=l i ) * x - ( Σri=l1 ) * L * x

設 A = Σri=l i,B = Σri=l1 ,可以發現對於線段樹中的每段區間A和B的值是固定的,這樣一來我們只需要累計每次修改的x以及L*x的值就可以順利下傳延遲修改了。

這道題就順利解決了。

// q.c

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long LL;
const int M=300000+10;
const int mod=(int)1e9+7;
struct Node {
	int l,r,sum,s1,s2;
	Node():l(0),r(0),sum(0),s1(0),s2(0) {}
};
struct SegmentTree {
	int root; Node nd[M<<2];
	SegmentTree():root(1) {}
	void update(int o) {
		nd[o].sum=((LL)nd[o<<1].sum+nd[o<<1^1].sum)%mod;
	}
	void pushdown(int o) {
		Node &p=nd[o],&lc=nd[o<<1],&rc=nd[o<<1^1];
		lc.sum=(lc.sum+(LL)p.s1*(lc.r+lc.l)*(lc.r-lc.l+1)/2)%mod;
		lc.sum=(lc.sum-(LL)p.s2*(lc.r-lc.l+1)%mod+mod)%mod;
		lc.s1=(lc.s1+p.s1)%mod;
		lc.s2=(lc.s2+p.s2)%mod;
		rc.sum=(rc.sum+(LL)p.s1*(rc.r+rc.l)*(rc.r-rc.l+1)/2)%mod;
		rc.sum=(rc.sum-(LL)p.s2*(rc.r-rc.l+1)%mod+mod)%mod;
		rc.s1=(rc.s1+p.s1)%mod;
		rc.s2=(rc.s2+p.s2)%mod;
		p.s1=p.s2=0;
	}
	void build(int o,int l,int r) {
		nd[o].l=l,nd[o].r=r;
		if(l!=r) {
			int mid=(l+r)>>1;
			build(o<<1,l,mid);
			build(o<<1^1,mid+1,r);
		}
	}
	void add(int o,int l,int r,int x) {
		Node &p=nd[o];
		if(l<=p.l&&p.r<=r) {
			p.sum=(p.sum+(LL)x*(p.r-l+p.l-l)*(p.r-p.l+1)/2)%mod;
			p.s1=(p.s1+x)%mod;
			p.s2=(p.s2+(LL)l*x)%mod;
		} else {
			if(p.s1||p.s1) pushdown(o);
			int mid=(p.l+p.r)>>1;
			if(l<=mid) add(o<<1,l,r,x);
			if(r>mid) add(o<<1^1,l,r,x);
			update(o);
		}
	}
	int query(int o,int l,int r) {
		Node p=nd[o];
		if(l<=p.l&&p.r<=r) return p.sum;
		else {
			if(p.s1||p.s2) pushdown(o);
			int mid=(p.l+p.r)>>1,ans=0;
			if(l<=mid) ans=((LL)ans+query(o<<1,l,r))%mod;
			if(r>mid) ans=((LL)ans+query(o<<1^1,l,r))%mod;
			return ans;
		}
	}
}t;
int n,m;
int main() {
	freopen("segment.in","r",stdin);
	freopen("segment.out","w",stdout);
	scanf("%d%d",&n,&m);
	t.build(t.root,1,n);
	int opt,l,r,x;
	for(int i=1;i<=m;i++) {
		scanf("%d%d%d",&opt,&l,&r);
		if(opt) scanf("%d",&x),t.add(t.root,l,r,x);
		else printf("%d\n",t.query(t.root,l,r));
	}
	return 0;
}

公式真的好難打啊。

[HZOI 2016]數列操作d