【做题】SRM701 Div1 Hard - FibonacciStringSum——数学和式&矩阵快速幂

原文链接 https://www.cnblogs.com/cly-none/p/SRM701Div1C.html

题意:定义"Fibonacci string"为没有连续1的01串。现在,给出\(a,b\),定义一个"Fibonacci string"的权值为\(x^a y^b\),其中\(x\)为0的个数,\(y\)为1的个数。

要求对所有长度为\(n\)的"Fibonacci string"的权值求和,对\(10^9 + 7\)取模。

\(n \leq 10^9, \ a, b \leq 25\)

显然第一反应就是矩阵存下所有\((a,b)\)做快速幂。然而,这样的话矩阵的边长是\(O(a^2)\)的,不能通过本题。

考虑最终答案的式子:
\[ \sum_{k=0}^n {n-k+1 \choose k} k^b (n-k)^a \]
我们尝试化简:
\[ \begin{aligned} & \sum_{k=0}^n {n-k+1 \choose k} k^b (n-k)^a \\ = & \sum_{k=0}^n \sum_{j=0}^a {n-k+1 \choose k} k^b {a\choose j} n^{a-j} (-k)^j \\ = & \sum_{j=0}^a {a \choose j} (-1)^j \sum_{k=0}^n {n-k+1 \choose k}k^{b+j} \end{aligned} \]
注意到后面的\(\sum_{k=0}^n {n-k+1 \choose k} k^{b+j}\)就是当\(a' = 0, \ b' = b+j\)时,所有长度为\(n\)的"Fibonacci string"的权值和。这时,我们再建矩阵,边长就只有\(O(a)\)了。最后\(O(a)\)枚举\(j\)就能计算出答案。

时间复杂度\(O(a^3 \log n)\)

#include <bits/stdc++.h>
using namespace std;

const int MOD = (int)(1e9 + 7), N = 110;
struct matrix {
  int n,m,mat[N][N];
  matrix(int n=0,int m=0): n(n), m(m) {
    memset(mat,0,sizeof mat);
  }
  matrix operator * (const matrix& a) const {
    assert(m == a.n);
    matrix ret = matrix(n, a.m);
    for (int k = 0 ; k < m ; ++ k)
      for (int i = 0 ; i < n ; ++ i)
    for (int j = 0 ; j < a.m ; ++ j)
      (ret.mat[i][j] += 1ll * mat[i][k] * a.mat[k][j] % MOD) %= MOD;
    return ret;
  }
};
matrix power(matrix a,int b) {
  assert(a.n == a.m);
  matrix ret = matrix(a.n, a.m);
  for (int k = 0 ; k < a.n ; ++ k)
    ret.mat[k][k] = 1;
  while (b) {
    if (b&1) ret = ret * a;
    a = a * a;
    b >>= 1;
  }
  return ret;
}
int power(int a,int b) {
  int ret = 1;
  while (b) {
    if (b&1) ret = 1ll * ret * a % MOD;
    a = 1ll * a * a % MOD;
    b >>= 1;
  }
  return ret;
}
class FibonacciStringSum {
public:
  int get( int n, int a, int b ) ;
};
int val[N],cmb[N][N];
int FibonacciStringSum::get(int n, int a, int b) {
  memset(cmb,0,sizeof cmb);
  for (int i = 0 ; i <= a + b ; ++ i)
    cmb[i][0] = 1;
  for (int i = 1 ; i <= a + b ; ++ i)
    for (int j = 1 ; j <= i ; ++ j)
      cmb[i][j] = (cmb[i-1][j] + cmb[i-1][j-1]) % MOD;
  matrix sta = matrix(1, 2 * (a + b + 1));
  matrix tran = matrix(2 * (a + b + 1), 2 * (a + b + 1));
  sta.mat[0][0] = 1;
  for (int i = 0 ; i <= a + b ; ++ i) {
    tran.mat[i][i] = 1;
    tran.mat[i + a + b + 1][i] = 1;
    for (int j = 0 ; j <= i ; ++ j)
      tran.mat[j][a + b + 1 + i] += cmb[i][j];
  }
  tran = power(tran, n);
  sta = sta * tran;
  for (int i = 0 ; i <= a + b ; ++ i)
    val[i] = (sta.mat[0][i] + sta.mat[0][i + a + b + 1]) % MOD;
  int ans = 0;
  for (int i = 0, t = 1 ; i <= a ; ++ i, t = -t)
    (ans += 1ll * t * cmb[a][i] * power(n, a - i) % MOD * val[b + i] % MOD) %= MOD;
  ans = (ans % MOD + MOD) % MOD;
  return ans;
}


小结:这个问题的特殊之处在于,既可以直接矩阵快速幂,也可以写成数学和式。然而,二者都不能直接解决这个问题。把两种方法相结合一直是常用的技巧(如分块),在这里也启示我们对于一个问题不能死板地但从一个方向来思考。

posted @ 2018-12-28 12:01 莫名其妙的aaa 阅读(...) 评论(...) 编辑 收藏