题目描述
卡德加喜欢养兔子。他在达拉然的下水道里放了N个兔笼(编号从1到N),里面养着他从德拉诺带来的兔子。它们的繁殖遵循斐波那契数列的规律:刚开始时,笼子里有一对刚出生的兔子。每对兔子在出生第二个月后,每个月都生一对兔子。(第一个月结束后有 1 对兔子。第二个月结束后有 2对。)
卡德加从苏拉玛的的大魔导师艾利桑德那边学习了先进的扭曲时空法术。有时候,他会对一排连续的兔笼(从第 L 号到第 R 号)释放时光流逝法术,让这些兔笼里的时间前进 K 个月。另外一些时候,他想喂一下兔子,所以他想知道第 L 号到第 R 号兔笼里有多少只兔子。
(假设这些操作都是在一个月以内完成的,不需要考虑自然时间对兔子的影响。)
输入
第一行两个整数N,M,表示兔笼的数量和操作的数量。
接下来 M 行,每行包含三个数 L,R,K。如果 K > 0,说明卡德加在使用时光流逝,编号 L 到 R 的兔笼时间前进 K 个月。如果 K = 0,说明他只是想喂兔子了,输出这些兔笼里有多少兔子。
输出
对每个喂兔子的操作,输出兔子的数量。答案模10007。
样例输入
10 10 1 3 2 1 1 0 2 4 0 3 5 0 4 7 3 3 5 0 1 4 0 2 7 0 1 9 4 2 10 0
样例输出
2 5 4 8 9 16 121
来源
2017年NOIP夏令营
看到了询问区间,马上想到线段树;看到了斐波那契数列,马上想到矩阵快速幂。问题在于怎么结合了。
XRJ神犇告诉我:
对于两个数列,如果这两个数列都具有斐波那契性质,则这两个数列的和也具有斐波那契性质。
什么意思呢?就是说对于两个数列{x1,x2,x3,x4,...},{y1,y2,y3,y4,...},如果x1+x2=x3, x2+x3=x4... , y1+y2=y3, y2+y3=y4, ...
如果有一个数列{z1,z2,z3,z4,...},其中z1=x1+y1, z2=x2+y2, ... zk=xk+yk, ...
则有z1+z2=z3, z2+z3=z4, ...
这个不难证明,利用等式的性质就好了。
利用这个特性,我们可以在线段树的每个地方存储运算时的矩阵(因为满足分配率)。更新的时候,用矩阵加法就可以了。
另外,此题还有一个优化:
CLZ巨神:斐波那契数列对10007取模时,第1个数与第20017个数是一样的,第2个数与第20018个数是一样的。
这样就可以预先算好20017个矩阵的值了,不需要使用快速幂。
代码如下:
#include <cstdio> #include <cstring> using namespace std; struct Matrix { int mat[2][2]; }; struct SegTree { int l,r; int add; Matrix val; }; const int maxn=100000, Mod=10007; const int CLZ=20016; //%%%CLZ int n,m; Matrix Fibo[CLZ]; //CLZ巨神告诉我们,斐波那契数列 %10007时每20016个一循环 SegTree b[maxn<<2|1]; //矩阵加法 Matrix jia(Matrix x, Matrix y) { Matrix z; for (int i=0; i<2; i++) for (int j=0; j<2; j++) z.mat[i][j]=(x.mat[i][j]+y.mat[i][j])%Mod; return z; } //矩阵乘法 Matrix cheng(Matrix x, Matrix y) { Matrix z; memset(z.mat,0,sizeof(z.mat)); for (int i=0; i<2; i++) for (int j=0; j<2; j++) { for (int k=0; k<2; k++) z.mat[i][j]+=x.mat[i][k]*y.mat[k][j]; z.mat[i][j]%=Mod; } return z; } void kuaidu(int &p) { char c; int f=1; p=0; do { c=getchar(); if (c==‘-‘) f=-1; } while (c<‘0‘||c>‘9‘); do { p=p*10+c-‘0‘; c=getchar(); } while (c>=‘0‘&&c<=‘9‘); p*=f; } void init() { kuaidu(n); kuaidu(m); Fibo[0].mat[0][0]=Fibo[0].mat[1][1]=1; Fibo[1].mat[1][0]=Fibo[1].mat[1][1]=Fibo[1].mat[0][1]=1; for (int i=2; i<CLZ; i++) Fibo[i]=cheng(Fibo[i-1],Fibo[1]); } void Build_Tree(int left, int right, int i) { b[i].l=left; b[i].r=right; if (left==right) { b[i].val.mat[0][0]=b[i].val.mat[1][1]=1; return; } int mid=left+right>>1; Build_Tree(left,mid,i<<1); Build_Tree(mid+1,right,i<<1|1); b[i].val=jia(b[i<<1].val,b[i<<1|1].val); } void Push_down(int i) { if (b[i].add!=0) { b[i].add%=CLZ; b[i<<1].add+=b[i].add; b[i<<1].add%=CLZ; b[i<<1|1].add+=b[i].add; b[i<<1].add%=CLZ; b[i<<1].val=cheng(b[i<<1].val,Fibo[b[i].add]); b[i<<1|1].val=cheng(b[i<<1|1].val,Fibo[b[i].add]); b[i].add=0; } } void Update_Tree(int left, int right, int value, int i) { if (left==b[i].l&&right==b[i].r) { b[i].add+=value; b[i].add%=CLZ; b[i].val=cheng(b[i].val,Fibo[value]); return; } Push_down(i); int mid=b[i].l+b[i].r>>1; if (right<=mid) Update_Tree(left,right,value,i<<1); else if (left>mid) Update_Tree(left,right,value,i<<1|1); else { Update_Tree(left,mid,value,i<<1); Update_Tree(mid+1,right,value,i<<1|1); } b[i].val=jia(b[i<<1].val,b[i<<1|1].val); } Matrix Query_Tree(int left, int right, int i) { if (left==b[i].l&&right==b[i].r) return b[i].val; Push_down(i); int mid=b[i].l+b[i].r>>1; if (right<=mid) return Query_Tree(left,right,i<<1); else if (left>mid) return Query_Tree(left,right,i<<1|1); else { Matrix lc=Query_Tree(left,mid,i<<1), rc=Query_Tree(mid+1,right,i<<1|1); return jia(lc,rc); } } void solve() { int x,y,v; kuaidu(x); kuaidu(y); kuaidu(v); if (x>y) { int temp=x; x=y; y=temp; } if (v==0) { Matrix T; memset(T.mat,0,sizeof(T.mat)); T.mat[0][0]=T.mat[0][1]=1; T=cheng(T,Query_Tree(x,y,1)); printf("%d\n",T.mat[0][0]); } else { v%=CLZ; if (v==0) return; Update_Tree(x,y,v,1); } } int main() { init(); Build_Tree(1,n,1); while (m--) solve(); return 0; }