题解 P4910 【帕秋莉的手环】

灯芯糕

2018-10-06 16:56:34

Solution

## 矩阵快速幂解法: 这是一个类似斐波那契数列的矩乘快速幂,所以推荐大家先做一下下列题目:(会了,~~差不多就是多倍经验题了~~) 注:如果你不会矩阵乘法,可以了解一下[P3390](https://www.luogu.org/problemnew/show/P3390)的题解 [P1939 【模板】矩阵加速(数列)](https://www.luogu.org/problemnew/show/P1939) [P3390 【模板】矩阵快速幂](https://www.luogu.org/problemnew/show/P3390) [P1306 斐波那契公约数](https://www.luogu.org/problemnew/show/P1306) [P1962 斐波那契数列](https://www.luogu.org/problemnew/show/P1962) [P4838 P哥破解密码](https://www.luogu.org/problemnew/show/P4838) 由题意可得:相邻两个珠子中必有金属性珠子。这其实就可以理解为不能有连续的两个木属性珠子。这样一看,此题就和[P4838 P哥破解密码](https://www.luogu.org/problemnew/show/P4838)差不多了。只不过这题是个2*2矩阵乘法 ### 进入正文: 我们先一次将1~n中每一个珠子的情况枚举 ```cpp // n=1 n=2 n=3 n=4 n=5 n=6 ....... //可放金属性珠子: 1 2 3 5 8 13 ....... //可放木属性珠子: 1 1 2 3 5 8 ....... ``` 不难发现这就是一个斐波那契数列的递推 ### 但是:这是一个手环! 所以第一个珠子与最后一个手环是相连的,他们会互相影响! 不过他们只会影响对方而不会影响其他珠子,我们可以将第一颗珠子选金属性与木属性这两种情况分开: ```cpp //第一颗珠子为金属性: 若 n=5 // 1 2 3 4 n ....... //金属性: 1 1 2 3 5 ....... //木属性: 0 1 1 2 3 ....... //第一颗珠子为木属性: n=5 // 1 2 3 4 n ....... //金属性: 0 1 1 2 3 ....... //木属性: 1 0 1 1 0 ....... //最后一颗不能为木! //两种情况加起来就是样例1的解了 ``` 所以此题就是求斐波那契数列**第n项** 加 **第n-1项的两倍** 然后就可矩阵快速幂了!递推矩阵如下 ```cpp // 1 1 // 0 1 ``` ## 不过我们当然不能止步于此: 因为还有一种更无脑有效的方法: 既然矩阵可以快速幂,那么说明每两个递推数(即答案)之间的递推矩阵是一样的! 所以我们可以先手算两组结果,然后直接推出递推矩阵: ```cpp // 3 4 乘 递推矩阵 = 4 7 // 解上述方程得递推矩阵为: // 0 1 // 3 4 乘 = 4 7 // 1 1 ``` ## 下面上代码: ```cpp #include<bits/stdc++.h> using namespace std; #define mod 1000000007//简化一下 struct ju{ long long a[2][2];//不用long long只有8分哦(亲测QAQ) ju operator *(const ju &x){ ju res;//这里需要另外建个矩阵存答案 memset(res.a,0,sizeof(res.a)); for(int i=0;i<2;i++) for(int j=0;j<2;j++) for(int k=0;k<2;k++) res.a[i][j]=(res.a[i][j]+a[i][k]%mod*(x.a[k][j])%mod)%mod; return res; } //重载运算符,(可以写成函数) }base,ans;//两个基本矩阵 int main(){ long long n,t; scanf("%lld",&t);//只有t就不写快读了 while(t--){ scanf("%lld",&n);n--;//n要减一,不然会错 QAQ base.a[0][0]=0;base.a[0][1]=1;base.a[1][0]=1;base.a[1][1]=1; ans.a[0][0]=2;ans.a[0][1]=1;ans.a[1][0]=0;ans.a[1][1]=0;//初始化 while(n){//快速幂,(也可以写成函数) if(n%2==1) ans=ans*base; base=base*base; n/=2; } printf("%lld\n",ans.a[0][1]%mod); } //输出 return 0; } ``` ### 代码中ans的初始化已经是n=1时的答案了所以n要减一。 啊,写题解好累啊,是我太蒟了吗。。