hdu5171 GTY's birthday gift(BestCoder Round #29 1002)

题意: 递推数列求和

  给一个有 n 个数的可重集 S ,然后进行 k 次下述操作:每次找出其中最大的两个数 a b ,把 a+b 这个元素加入 S .问最后可重集 S 的元素和,结果对 10000007 取余.
   2n100000,1k1000000000

符号定义

符号 说明
a,b 假设 ab
{di}
di=aa+bdi1+di2(i=0)(i=1)(i>1)
Si
Si={Si1+di(i=0)(i>0)

方法一: 利用周期性

  因为是对MOD=10000007取余,所以 di 肯定存在一个值不大于MOD的周期c,即有 di+c=di .(请思考并理解这段话后再看后文)
  

对应代码中的第44~52行
  c的算法:利用 di 的递推式计算,当出现 di=adi1=b 时,此时的i就是c的值.
  用s1存储一段周期的元素和.
  
  这段代码结束后,有两种情况:①循环正常结束,表示k比较小,还不用算出c,已经得到答案了.②循环被break,表示此时的i即为c的值.
  两种情况可以用第51~59行的代码统一处理.

  第5~14行是读入外挂,第27~37行是在数组中找出最大的两个数的一种方法.

 #include <algorithm>
 #include <iostream>
using namespace std;

 #define ISDIG ((c=getchar())>='0'&&c<='9')
template <typename T>
inline int read(T& x) {
    int c, sign = 1; x = 0;
    while (!(ISDIG || c == '-')) if (c == EOF) return c;
    if (c == '-') sign = -1; else x = x*10 + c - '0';
    while (ISDIG) x = x*10 + c - '0';
    x *= sign;
    return 1;
}

const int MOD = 10000007;
int d[MOD + 10];

int main() {
    int n, k, a, b;
    int t, i; // 临时变量、计数器

    while (cin >> n >> k) {
        read(a); read(b);
        int s = a + b;
        for (i = 2; i < n; i++) {
            //{找出初始S中最大的两个数a,b
            read(t);
            if (t > a) {
                if (t > b) {
                    if (a < b) a = t;
                    else b = t;
                }
                else a = t;
            }
            else if (t > b) b = t;
            //}
            s = (s + t) % MOD;
        }
        if (b > a) swap(a , b); // 假设a>=b
        d[0] = a;
        d[1] = a + b;

        //{计算出周期c和这段周期的元素和s1
        int c, s1 = d[1];
        for (i = 2; i <= k; i++) {
            d[i] = (d[i-1] + d[i-2]) % MOD;
            s1 = (d[i] + s1) % MOD;
            if (d[i] == a && d[i-1] == b) break;
        }
        c = i;
        //}

        do {
            s = (s + s1) % MOD;
            k -= c;
        } while (k > c);
        // 去掉完整的周期后,还有些残余的末尾项要加上
        for (i = 1; i <= k; i++) s = (d[i] + s) % MOD;

        cout << s << "\n";
    }

    return 0;
}

  注意也只有64MB的内存限制才可以用这个方法,因为32MB只能开辟800万大小的一个int数组╮(╯▽╰)╭.

方法二: 矩阵快速幂模

  矩阵快速幂模的解法原理,我就不讲这么详细了.去年做过这道比较基础的矩阵快速幂模题:FOJ 1683 纪念SlingShot,那道题理解后,此类题型的基本思想就掌握了,这题难度差不多.
   FOJ 1692 Key problem这篇博客讲了道更难的矩阵快速幂模题.

  这题关键是推出如下的公式:

100111110kS0ab=Skdkdk1

  借着做这题的机会,开发个新的矩阵类.

 #include <algorithm>
 #include <cstring>
 #include <iostream>
using namespace std;

 #define ISDIG ((c=getchar())>='0'&&c<='9')
template <typename T>
inline int read(T& x) {
    int c, sign = 1; x = 0;
    while (!(ISDIG || c == '-')) if (c == EOF) return c;
    if (c == '-') sign = -1; else x = x*10 + c - '0';
    while (ISDIG) x = x*10 + c - '0';
    x *= sign;
    return 1;
}

/*矩阵类(用于快速幂模)
  1、T是整型类型,N是方阵大小,MOD是取余的值
  2、Eye是单位阵
*/
template <typename T, const int N, const int MOD>
class Matrix {
    T val[N][N];

public:
    Matrix() { memset(val, 0, sizeof(val)); }
    Matrix(T a[N][N]) { memcpy(val, a, sizeof(val)); }

    Matrix operator*(const Matrix& c) const {
        Matrix res;
        for (int i = 0; i < N; ++i)
            for (int j = 0; j < N; ++j)
                for (int k = 0; k < N; ++k) {
                    res.val[i][j] += val[i][k] * c.val[k][j];
                    //防止矩阵元素变为负数,若不需要,去掉"+MOD"
                    res.val[i][j] = (res.val[i][j] + MOD) % MOD;
                }
        return res;
    }

    Matrix& operator*=(const Matrix& c) {
        *this = *this * c;
        return *this;
    }

    Matrix operator^(int k) const { //返回*this^k
        Matrix res = Eye();
        Matrix step(*this);
        while (k) {
            if (k & 1) res *= step;
            k >>= 1;
            step *= step;
        }
        return res;
    }

    Matrix Eye() const {
        Matrix a;
        for (int i = 0; i < N; i++) a.val[i][i] = 1;
        return a;
    }

    void out() const {
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++)
                cout << val[i][j] << " ";
            cout << "\n";
        }
    }

    T* operator[](int i) { return val[i]; }
};

typedef long long LL;
const int MOD = 10000007;

int main() {
    int n, k, a, b, t;

    while (cin >> n >> k) {
        read(a); read(b);
        int s = a + b;

        for (int i = 2; i < n; i++) {
            //{找出初始S中最大的两个数a,b
            read(t);
            if (t > a) {
                if (t > b) {
                    if (a < b) a =t;
                    else b = t;
                }
                else a = t;
            }
            else if (t > b) b = t;
            //}
            s = (s + t) % MOD;
        }
        if (b > a) swap(a, b); // 假设a>=b

        LL aa[3][3]={{1,1,1},{0,1,1},{0,1,0}};
        Matrix<LL,3,MOD> A(aa), X, Y;
        X[0][0] = s, X[1][0] = a, X[2][0] = b;
        Y = (A^k) * X;
        cout << Y[0][0] << "\n";
    }

    return 0;
}

你可能感兴趣的:(acm题目,递推数列,矩阵快速幂模)