【Author : DS】Regionals 2010, Asia - Amritapuri - E 题解

Sales Prediction

题目地址:

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值