PKU 3734 Blocks

http://poj.org/problem?id=3734
n個に並べた直線状のブロックの色の塗り方を数える問題。
ただし、色は4色あり、赤と緑は偶数個でないといけない。

漸化式をたてて、繰り返し二乗法を用いる。
n個並べたときに、赤と緑が偶数個ある組み合わせの数をee_nとし、赤と緑が奇数この組み合わせの数をoo_nとして、それ以外をeo_nとすると
ee_n=2*ee_{n-1}+eo_{n-1}
eo_n=2*ee_{n-1}+2*eo_{n-1}+2*oo_{n-1}
oo_n=2*oo_{n-1}+ev_{n-1}
というような漸化式が立てられるので、行列の掛け算に直しました。

typedef vector<int> vi;
typedef vector<vi> mat;

mat operator*(const mat&l,const mat&r){
  mat ret(SZ(l),vi(SZ(r[0])));
  rep(i,SZ(l)){
    rep(j,SZ(r[0])){
      ret[i][j]=0;
      rep(k,SZ(l[0]))ret[i][j]=(ret[i][j]+l[i][k]*r[k][j])%10007;
    }
  }
  return ret;
}

mat ki;

mat pow(const mat&in,int p){
  mat ret;
  if(p==1)return ki;
  if(p%2){
    mat tt=pow(in,p/2);
    ret=ki*tt*tt;
  }else{
    mat tt=pow(in,p/2);
    ret=tt*tt;
  }
  return ret;
}

main(){
  int test;
  cin>>test;
  int a[3][3]={{2,1,0},
              {2,2,2},
              {0,1,2}};
  ki.resize(3);
  ki[0].assign(a[0],a[0]+3);
  ki[1].assign(a[1],a[1]+3);
  ki[2].assign(a[2],a[2]+3);
  
  while(test--){
    ll n;
    cin>>n;
    mat ans=pow(ki,n);
    cout<<ans[0][0]%10007<<endl;
  }
}