http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=11994
今天1Y了E题,对着俩2B的样例足足调了2.5h,不过1Y的时候相当兴奋。索性写个解题报告吧。
n , r , k 分别是 需要考察的n项 , "猥"Fibonacci 数列的阶数 , 考察数字的下表差。s1~sr给前r个数,a1~ar “猥”Fibonacci数列的系数。求 S_{k} + S_{k * 2} + ... + S_{k * n}
n < 1e9 , r , k <=8
看到Fib 数列,首先想到的就是矩阵乘法。介于矩阵乘法只能用logN的时间算出第N项,就想到用 E + A + A^{2} + ... + A^{k} 的形式累加。所以只用算出来相邻k项之间的转移矩阵,然后用log(n)的时间计算 E + A + A^{2} + ... + A^{k} 即可。比赛的时候想麻烦了。。。
1、求 r , k 的LCM = C 。 在每一段C中划分等价类 , 将 Const * c + j * k 化为一个等价类。并且n *= k。 也就是算1~n中 i % k == 0 的 Si 之和
2、预处理 1 ~ C 的所有值 , 暴力O(C * C )。
3、枚举 1 ~ C 中,遇到 i % k == 0 时说明找到了一类, 如果i >= r 那么做4 , 否则 ans = ans + a[i] , i += C 然后做4 。 最后累加即可 O(100)
4、3中,已经找到了某一个等价类的开始 i (i % k == 0) 并且 i >= C 。那么首先用Fib的方法建立1~r -> 2 - r+1 的转移矩阵 Meng , [ O(r * r) ] , 然后C次方求出对于一个等价类的转移矩阵(即从 x * k 转移到 x * k + C的转移矩阵) O(log(C) * r * r * r) 。 并将 k - r ~ k 放到向量 Meng2 中
5、将矩阵放入如下图的分块矩阵ZHU中,并且 n / c + 1 次方 得到 E + A + A^{2} + ... + A^{k} 矩阵 O(log(n) * 2 * 2 * 2 * r * r * r)
6、将E + A + A^{2} + ... + A^{k} 矩阵乘以Meng2 那么得到的Meng2的(0,0)号位置就是此等价类的和 O(1 * r * r)
7、累加,输出答案。
所以最终的复杂度就是 O(log(n) * r ^ 3 * Const)
Code:
#include <iostream> #include <stdio.h> #include <string.h> using namespace std; const int MATN = 10; #define LL long long const LL modo =1000000007LL; struct Mat{ //矩阵类 int Matn , Matm; LL a[MATN][MATN]; Mat(){ Matn = 0; Matm = 0; memset(a , 0 , sizeof(a)); } void output(){ cout << "OUTPUT" << endl; for (int i = 0 ; i < Matn ; ++i){ for (int j = 0 ; j < Matm ; ++j){ printf("%d ",a[i][j]); } printf("\n"); } } void init(){ Matn = 0;Matm = 0; memset(a , 0 , sizeof(a)); } Mat operator + (const Mat & A) const{ Mat ret = A; for (int i = 0 ; i < Matn ; ++i){ for (int j = 0 ; j < Matm ; ++j) ret.a[i][j] = (ret.a[i][j] + a[i][j]) % modo; } return ret; } Mat operator * (const Mat & A) const{ Mat c ; c.Matn = Matn; c.Matm = A.Matm; for (int i = 0 ; i < Matn ; ++i) for (int j = 0 ; j < A.Matm ; ++j) for (int k = 0 ; k < Matm ; ++k) c.a[i][j] = (c.a[i][j] + (a[i][k] * A.a[k][j])%modo)%modo; return c; } void initI(){ memset(a, 0 , sizeof(a)); for (int i = 0 ; i < Matn ; ++i) a[i][i] = 1; } Mat power(LL k){ Mat c = *this , b; b.init(); b.Matn = Matn ; b.Matm = Matm; b.initI(); while(k){ if (k & 1LL) b = b * c; c = c * c; k >>= 1LL; } return b; } }; LL gcd(LL a, LL b){ return b ? gcd(b , a % b) : a; } LL GCD[10][10]; void init(){ //预处理GCD for (int i = 1 ; i <= 8 ; ++i) for(int j = 1; j <= 8 ; ++j) GCD[i][j] = gcd(i , j); } const int N = 200; LL s[N] , a[N] ; LL n , k , r; LL c; Mat Meng , res , Meng2; struct ZHU{ //用来计算 E + A + ... Mat a[2][2]; void init(Mat p){ for (int i = 0 ; i < 2 ; ++i) for (int j= 0 ; j < 2 ;++j){ a[i][j].init(); a[i][j].Matn = a[i][j].Matm = r; } a[0][0] = p; a[0][1].initI(); a[1][1].initI(); } void initI(){ for (int i = 0 ; i < 2 ; ++i) for (int j= 0 ; j < 2 ;++j){ a[i][j].Matn = a[i][j].Matm = r; a[i][i].initI(); } } ZHU operator * (const ZHU & A) const{ ZHU res; for (int i = 0 ; i < 2 ; ++i) for (int j= 0 ; j < 2 ;++j){ res.a[i][j].init(); res.a[i][j].Matn = res.a[i][j].Matm = r; } 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] * A.a[k][j]); return res; } Mat power(LL k){ ZHU c = *this , b; bool f = false; while(k){ if (k & 1LL){ if (!f) b = c; else b = b * c; f = true; } c = c * c; k >>= 1LL; } if (!f){ b = c; b.a[0][1].initI(); } return b.a[0][1]; } }; ZHU zhu; void solve(){ scanf("%lld%lld%lld",&n , &r , &k); for (int i = 1; i <= r ; ++i) scanf("%lld", &s[i]); for (int i = 1 ; i <= r ; ++i) scanf("%lld",&a[i]); c = k * r / gcd(k , r); //计算循环节 for (int i = r + 1; i <= 2 * c ; ++i){ //预处理循环内S的值 s[i] = 0; for (int j = 1 ; j <= r ; ++j) s[i] = (s[i] + (a[j] * s[i - j]) % modo) %modo; } LL ans = 0; n *= k; for (int i = 1; i <= c && i <= n ; ++i){ //在循环节内枚举等价类的开始并且计算 if (i % k != 0)continue; //printf("%d %d DS\n",i , (n - i) / c + 1); Meng.init(); Meng.Matm = Meng.Matn = r; for (int j = 0 ; j < r ; ++j) Meng.a[j][0] = a[j + 1]; for (int j = 0 ; j < r ; ++j) Meng.a[j][j + 1] = 1LL; //Meng.output(); Meng= Meng.power(c); int ii; if (i <= r){ // 如果 i <= r ,那么从下一个k开始 ans = (ans + s[i]) % modo; ii = c + i; } else ii = i; if (ii > n) continue; //Meng.output(); Meng2.init(); //将前r项放到向量中 Meng2.Matn = 1; Meng2.Matm = r; for(int j = 0 ; j < r ; ++j) Meng2.a[0][j] = s[ii - j]; zhu.init(Meng); res = zhu.power( (n - ii) / (c) + 1); // 计算 E + A... //res.output(); Meng2 = Meng2 * res; // 计算此等价类的累加和 //cout << "DS:" << Meng2.a[0][0] << endl; ans = (ans + Meng2.a[0][0]) % modo; //放入ans中。。 } printf("%lld\n",ans); } int main(){ int _; scanf("%d", &_); while (_--) solve(); } /* 2 3 2 3 1 1 1 1 */
这个题目一开始想的方法方向对了, 很自豪。不过错误的地方太多,分块矩阵乘法也没有写过,比较生疏。不过是一个好题,巩固了矩阵乘法,也练了一把Coding能力。好题啊Orz。传说现场赛只有仨人过- -爽翻了!
屌丝和孟神在长春要加油。BUAA && CUGB要给力~
DSLoveMz