题目描述
卡德加有N个兔子笼,编号从1~N。刚开始每个兔子笼里有1对小兔,每个月小兔会长成大兔,之后的每个月这对大兔都会生出1对小兔。即兔子的繁殖遵循斐波那契数列的规律。
例如:第一个月1对小兔,第二个月1对大兔,第三个月1对小兔1对大兔,第四个月1对小兔2对大兔,第五个月2对小兔3对大兔……
卡德加学会了时间膜法,他可以对任意的[L,R]区间施加时间流逝K个月的膜法(如果他想要的话),使得这之中的兔子变为K个月后的景象。
卡德加有时想喂兔子,所以他需要知道任意的[L,R]区间内的兔子数量,即求出[L,R]区间内共有多少对兔子,大兔和小兔都需要算上。
这些操作都是在一个月内完成的,所以不必考虑自然时间流逝对兔子的影响。
输入
第一行两个正整数N,M,表示兔子笼的数目和操作的数目。
接下来M行,每行三个整数L,R,K。如果K>0,表示卡德加想对区间[L,R]施放时间流逝K个月的膜法。如果K=0,表示卡德加想要喂兔子了,输出区间[L,R]内兔子总共有多少对。
输出
对每个喂兔子的操作,输出一行,一个数,表示兔子的对数。答案对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
题解
很显然的线段树类型题,但是操作不简单。
考虑区间[L,R]内有A只小兔,B只大兔,那么经过K个月后,会有多少只呢?
根据斐波那契数列的递推式,写出矩阵,再写出公式:
(egin{bmatrix}0&1\1&1end{bmatrix}^{K}cdotegin{bmatrix}A\Bend{bmatrix}=egin{bmatrix}A'\B'end{bmatrix})
A'和B'就是K月之后的小兔和大兔数量。
对于矩阵乘方,可以使用矩阵快速幂。之后就可以和普通的线段树一样操作。
需要注意的是,这题有点卡常,所以注意对矩阵乘法和其它细节上的常数优化。
优化①:对于2*2的小矩阵,不写结构体,不用循环,直接乘,会加快速度。
优化②:这是一个很有技巧的优化,注意到矩阵(egin{bmatrix}0&1\1&1end{bmatrix}^{20016})在模10007意义下等于单位矩阵,即K=K%20016对结果没有影响。根据这一点可以进行优化。这一点是如何想到的呢?首先应该想到,在模意义下,一切递推式均会重复。然后可以打表找规律,找循环节。对于斐波那契数列,有定理宣称,在模意义下,循环节长度不会超过模数的6倍。
优化③:继续在②的基础上优化,预处理出矩阵的0~20015次幂,做乘法时直接用,直接消灭了log和矩阵乘法的常数。
代码如下:
1 #include<cstdio> 2 #include<cstring> 3 #define F(i,a,b) for(int i=a;i<=b;++i) 4 #define Mod 10007//题目要求的模数 5 #define Mod2 20016//循环节长度 6 #define getchar() (S==TT&&(TT=(S=BB)+fread(BB,1,1<<15,stdin),TT==S)?EOF:*S++) 7 char BB[1<<15],*S=BB,*TT=BB;//快读而已 8 inline int I(){//快读 9 int x=0;char c=getchar(); 10 while(c<'0'||c>'9')c=getchar(); 11 while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+c-'0',c=getchar(); 12 return x; 13 } 14 int a1[262145],a2[262145],lazy[262145]; 15 int A00[20016],A01[20016],A10[20016],A11[20016];//处理前20016次幂 16 int n,m,a,b,k,tmpa1,tmpa2; 17 inline int mo(int x){return x<Mod2?x:x-Mod2;} 18 void init(){//预处理过程 19 A00[0]=A11[0]=1, A01[0]=A10[0]=0; 20 F(i,1,20015) A00[i]=A01[i-1], A01[i]=(A00[i-1]+A01[i-1])%Mod, A10[i]=A11[i-1], A11[i]=(A10[i-1]+A11[i-1])%Mod; 21 } 22 void Mogic(int&x,int&y,int K){//对[x,y]施放时间流逝K月 23 tmpa1=(A00[K]*x+A01[K]*y)%Mod, tmpa2=(A10[K]*x+A11[K]*y)%Mod; 24 x=tmpa1, y=tmpa2; 25 } 26 inline void push_down(int l,int r,int i){ 27 if(!lazy[i]) return; 28 Mogic(a1[i<<1],a2[i<<1],lazy[i]); 29 Mogic(a1[i<<1|1],a2[i<<1|1],lazy[i]); 30 lazy[i<<1]=mo(lazy[i<<1]+lazy[i]); 31 lazy[i<<1|1]=mo(lazy[i<<1|1]+lazy[i]); 32 lazy[i]=0; 33 } 34 void build(int l,int r,int i){ a1[i]=(r-l+1)%Mod; if(l<r) build(l,(l+r)>>1,i<<1), build(((l+r)>>1)+1,r,i<<1|1);} 35 void Xu(int l,int r,int i){//时间流逝 36 if(r<a||b<l) return; 37 if(a<=l&&r<=b) { Mogic(a1[i],a2[i],k); lazy[i]=mo(lazy[i]+k); return; } 38 push_down(l,r,i); 39 Xu(l,(l+r)>>1,i<<1); 40 Xu(((l+r)>>1)+1,r,i<<1|1); 41 a1[i]=(a1[i<<1]+a1[i<<1|1])%Mod; 42 a2[i]=(a2[i<<1]+a2[i<<1|1])%Mod; 43 } 44 int Feed(int l,int r,int i){//喂兔子 45 if(r<a||b<l) return 0; 46 if(a<=l&&r<=b) return (a1[i]+a2[i])%Mod; 47 push_down(l,r,i); 48 return (Feed(l,(l+r)>>1,i<<1)+Feed(((l+r)>>1)+1,r,i<<1|1))%Mod; 49 } 50 int main(){ 51 init(); 52 n=I(),m=I(); 53 build(1,n,1);//建树 54 F(i,1,m){ 55 a=I(),b=I(),k=I(); 56 if(k) { if(k%=Mod2) Xu(1,n,1); }//这里如果k是20016的倍数,就不用动了 57 else printf("%d ",Feed(1,n,1)); 58 } 59 return 0; 60 }