【9018题解】2109 卡德加的兔子

题目描述

卡德加有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个月后,会有多少只呢?
根据斐波那契数列的递推式,写出矩阵,再写出公式:
\(\begin{bmatrix}0&1\\1&1\end{bmatrix}^{K}\cdot\begin{bmatrix}A\\B\end{bmatrix}=\begin{bmatrix}A'\\B'\end{bmatrix}\)
A'和B'就是K月之后的小兔和大兔数量。
对于矩阵乘方,可以使用矩阵快速幂。之后就可以和普通的线段树一样操作。
需要注意的是,这题有点卡常,所以注意对矩阵乘法和其它细节上的常数优化。
优化①:对于2*2的小矩阵,不写结构体,不用循环,直接乘,会加快速度。
优化②:这是一个很有技巧的优化,注意到矩阵\(\begin{bmatrix}0&1\\1&1\end{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\n",Feed(1,n,1));
58     }
59     return 0;
60 }
posted @ 2017-08-25 22:40  粉兔  阅读(405)  评论(0编辑  收藏  举报