coderfoces446c (斐波那契数列)
题目描述:
区间增值,但是每一项增加的值为Fi - l + 1,F[i]为斐波那契数列,求区间和?
考虑线段树,刚开始想用斐波那契数列的前n项和,可是推不出来,考虑到每个区间的增值序列都是一段斐波那契数列,他们的和是否有什么特性呢?
发现如果前两项为a和b的话,那么,a,b,a+b,a+2b,2a+3b,3a+5b;
a和b前的系数为斐波那契数列(后一项为前两项之和,
设F[k]表示以a,b开头的第k项的值,s[k]代表以a和b开头的前k项和
F[k]=a*f[k-2]+b*f[k-1];
F[1]=1*a+0*b;
F[2]=0*a+1*b;
F[3]=f[1]*a+f[2]*b;
F[4]=f[2]*a+f[3]*b;
F[k]=f[k-2]*a+f[k-1]*b;
pp[k]=1+0+f[1]+f[2]+f[3]+...f[k-2];
qq[k]=0+f[1]+f[2]+f[3]+...+f[k-1];
求和:
S[k]=a*pp[k]+b*qq[k];
这样只需要确定每个区间的a和b,长度可以计算出来,那么第k项可以求出来,前k项和也可以求出来;
维护每个区间的a和b的值,a和b作为标记。
写这道题是发现对标记的处理有了更深的理解:
标记会有那些操作呢?
1 位置,最下层的标记以下的节点没有被更新,最上层的标记以上全都被更新过,
也就是对于每一个标记来说,它没有更新它所在节点的子节点,更新了它所有的父节点。
2 标记在同一个区间可以累加(累加型标记)
3 标记传递时,子节点的值是由父节点的标记累计改变的,子节点的标记只是用来往下传的,所以子节点的标记值没有用。
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <cstring> #include <algorithm> #define LL long long #define MOD 1000000009 using namespace std; //线段树 //区间每点增值,求区间和 const int maxN = 310000; struct node { int lt, rt; int addA,addB; LL val; }tree[4*maxN]; LL a[maxN]; int n,m; LL f[maxN]; LL pp[maxN]; LL qq[maxN]; void init() { memset(f,0,sizeof(f)); f[1]=1; f[2]=1; pp[1]=1; pp[2]=1; qq[1]=0; qq[2]=1; for(int i=3;i<maxN;i++) { f[i]=(f[i-1]+f[i-2])%MOD; pp[i]=(pp[i-1]+f[i-2])%MOD; qq[i]=(qq[i-1]+f[i-1])%MOD; } } //向下更新 void pushDown(int id) { if (tree[id].addA != 0 || tree[id].addB!=0) { LL a,b; int LeftLen= tree[id << 1].rt - tree[id << 1].lt +1; a=tree[id].addA; b=tree[id].addB; tree[id<<1].addA += a; tree[id<<1].addB += b; tree[id<<1].addA %=MOD; tree[id<<1].addB %=MOD; tree[id<<1].val += ( ( (pp[LeftLen]* a)%MOD + (qq[LeftLen] *b)%MOD )%MOD ) ; tree[id<<1].val %= MOD; int RightLen= tree[id << 1 |1].rt - tree[id <<1 |1].lt +1; a=( ( (tree[id].addA * f[LeftLen+ 1-2] )%MOD + (tree[id].addB * f[LeftLen +1 -1])%MOD ) %MOD ); b=( ( (tree[id].addA * f[LeftLen+1-2 +1])%MOD + (tree[id].addB * f[LeftLen +1 -1 +1])%MOD )%MOD); //a和b分别为第k项和第k+1项 tree[id<<1 |1].addA +=a; tree[id<<1 |1].addB +=b; tree[id <<1 |1].addA%=MOD; tree[id <<1 |1].addB%=MOD; tree[id<<1 |1].val +=( ( (pp[RightLen] *a) %MOD + (qq[RightLen] *b)%MOD ) %MOD ); tree[id <<1 |1].val%=MOD; tree[id].addA = 0; tree[id].addB = 0; } } //向上更新 void pushUp(int id) { tree[id].val = ( (tree[id<<1].val + tree[id<<1|1].val) %MOD); } //建立线段树 void build(int lt, int rt, int id) { tree[id].lt = lt; tree[id].rt = rt; tree[id].val = 0;//每段的初值,根据题目要求 tree[id].addA = 0; tree[id].addB = 0; if (lt == rt) { tree[id].val = a[lt]; return; } int mid = (lt+rt)>>1; build(lt, mid, id<<1); build(mid+1, rt, id<<1|1); pushUp(id); } //增加区间内每个点固定的值 void add2(int lt, int rt, int id, int Left) { if (lt <= tree[id].lt && rt >= tree[id].rt) { int plsa= tree[id].lt - Left +1; int plsb= tree[id].lt - Left +2; tree[id].addA += f[plsa]; tree[id].addB += f[plsb]; tree[id].addA%=MOD; tree[id].addB%=MOD; LL a,b; a=f[plsa]; b=f[plsb]; int Len= tree[id].rt - tree[id].lt + 1; tree[id].val +=( (a*pp[Len])%MOD + (b*qq[Len])%MOD )%MOD ; tree[id].val %=MOD; return; } pushDown(id); //区间更新中最重要的lazy操作,把下次可能要查询的节点的标记更新到,然后只要不影响查询就好。 int mid = (tree[id].lt+tree[id].rt)>>1; if (lt <= mid) add2(lt, rt, id<<1, Left); if (rt > mid) add2(lt, rt, id<<1|1, Left); pushUp(id); } //查询某段区间内的和 LL query(int lt, int rt, int id) { if (lt <= tree[id].lt && rt >= tree[id].rt) return tree[id].val; pushDown(id); //如果不pushdown的话,它的计算方式是记下沿途的标记,到达目的节点之后计算目的节点自上而下的标记之和; //然后再加上本节点之前由自下而上的标记递推的来的和也就是tree.val(tree.val的含义就是只执行节点id及其子孙节点中的add操作,节点id对应区间中所有数之和) //如果每次pushdown就可以把下次可能要查询的节点的标记更新到就好(tree.val的含义就是执行所有对节点id有影响的id操作,节点id对应区间中所有数之和) int mid = (tree[id].lt+tree[id].rt)>>1; LL ans = 0; if (lt <= mid) ans += query(lt, rt, id<<1); if (rt > mid) ans += query(lt, rt, id<<1|1); ans%=MOD; return ans; } int main() { //freopen("test.txt","r",stdin); init(); while(~scanf("%d%d",&n,&m)) { for(int i=1;i<=n;i++) scanf("%d",&a[i]); build(1,n,1); for(int i=1;i<=m;i++) { int c,l,r; scanf("%d%d%d",&c,&l,&r); if(c==1) { add2(l,r,1,l); } if(c==2) { printf("%I64d\n",query(l,r,1)); } } } return 0; }