TopCoder SRM 428 Div1 Medium TheLongPalindrome

問題

英小文字からなる、長さがn文字以下で、使われているアルファベットがk種類以下であるような回文の数をmod 1234567891で求めよ。

制約条件

n≦10^9
k≦26

方針

長さnの回文は、長さ(n+1)/2の文字列の数に等しい。
なので、アルファベットがk種類以下で、長さ(n+1)/2の文字列の総数をn=1からnまで求めればよい。


行列で求める。
列ベクトルF(n)を、i番目の成分が「長さnの文字列で、アルファベットがちょうどi文字だけ登場するようなものの総数」とする。
すると、
[0 26 0 …… 0 ]
[0 1 25 …… 0 ]
……
[0 0 0 …… 1 ]
[0 0 0 …… 26]
であるような行列Aを使って、
F(n+1)=F(n)*Aと表すことができる。


行列の累乗の和は蟻本にあるように、
[A O]
[I I]
の行列を累乗することで求めることができる。

ソースコード

const int mod=1234567891;
typedef vector<vi> mat;
mat operator*(mat &a,mat &b){
  mat c(a.size(),vi(b[0].size()));
  rep(i,c.size())rep(j,c[0].size())rep(k,a[0].size())
    (c[i][j]+=a[i][k]*b[k][j])%=mod;
  return c;
}
mat pow(mat a,int n){
  mat res(a.size(),vi(a[0].size()));
  rep(i,res.size())res[i][i]=1;
  for(;n;n/=2){
    if(n&1)res=res*a;
    a=a*a;
  }
  return res;
}
mat powsum(mat &a,int n){
  int m=a.size();
  mat b(2*m,vi(2*m));
  rep(i,m)rep(j,m)b[i][j]=a[i][j];
  rep(i,m)b[i+m][i]=b[i+m][i+m]=1;
  b=pow(b,n);
  mat res(m,vi(m));
  rep(i,m)rep(j,m)res[i][j]=b[i+m][j];
  return res;
}
ll calc(int n,int k){
  mat a(27,vi(27));
  rep(i,26)a[i+1][i+1]=i+1, a[i][i+1]=26-i;
  mat b=powsum(a,n); b=b*a;
  
  mat c(1,vi(27));
  c[0][0]=1;
  c=c*b;
  ll ans=0;
  rep(i,k)ans+=c[0][i+1];
  return ans%mod;
}

class TheLongPalindrome {
  public:
  int count(int n, int k) {
    return (calc(n/2,k)+calc((n+1)/2,k))%mod;
  }
};