結果

問題 No.563 超高速一人かるた large
ユーザー koyumeishikoyumeishi
提出日時 2017-06-18 15:15:30
言語 C++14
(gcc 12.3.0 + boost 1.83.0)
結果
AC  
実行時間 305 ms / 3,000 ms
コード長 11,299 bytes
コンパイル時間 2,220 ms
コンパイル使用メモリ 143,656 KB
実行使用メモリ 34,392 KB
最終ジャッジ日時 2024-04-09 19:46:59
合計ジャッジ時間 5,441 ms
ジャッジサーバーID
(参考情報)
judge3 / judge1
このコードへのチャレンジ
(要ログイン)

テストケース

テストケース表示
入力 結果 実行時間
実行使用メモリ
testcase_00 AC 6 ms
13,028 KB
testcase_01 AC 6 ms
13,156 KB
testcase_02 AC 6 ms
13,156 KB
testcase_03 AC 9 ms
13,592 KB
testcase_04 AC 13 ms
14,324 KB
testcase_05 AC 12 ms
14,588 KB
testcase_06 AC 63 ms
19,104 KB
testcase_07 AC 91 ms
19,208 KB
testcase_08 AC 269 ms
24,320 KB
testcase_09 AC 305 ms
25,924 KB
testcase_10 AC 187 ms
14,344 KB
testcase_11 AC 279 ms
14,308 KB
testcase_12 AC 194 ms
14,052 KB
testcase_13 AC 196 ms
14,184 KB
testcase_14 AC 164 ms
14,224 KB
testcase_15 AC 75 ms
13,924 KB
testcase_16 AC 190 ms
14,476 KB
testcase_17 AC 28 ms
18,396 KB
testcase_18 AC 177 ms
15,272 KB
testcase_19 AC 39 ms
34,392 KB
testcase_20 AC 170 ms
17,404 KB
権限があれば一括ダウンロードができます

ソースコード

diff #

/*
O(n^2 log n) dp + NTT
*/

#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <random>
#include <algorithm>
#include <set>
#include <map>
#include <chrono>
using namespace std;

void timer(){
  static chrono::steady_clock::time_point start = chrono::steady_clock::now();
  auto now = chrono::steady_clock::now();
  cerr << "elapsed time : " << chrono::duration_cast<chrono::milliseconds>(now-start).count() << "[ms]" << endl;
}

const long long mod = 1000000007;

long long ans[10001];
long long fact[10001];
long long fact_inv[10001];

long long mod_pow(long long x, long long y, long long mod){  //(x^y) % mod
  if(x==0 && y!=0) return 0;
  long long ret=1LL;
  while(y>0LL){
    if(y&1LL) ret = (ret * x) % mod;
    x = (x*x) % mod;
    y >>= 1LL;
  }
  return ret;
}

long long extgcd(long long a, long long b, long long &x, long long &y){
  long long d=a;
  if(b!=0){
    d = extgcd(b, a%b, y, x);
    y -= (a/b) * x;
  }else{
    x = 1;
    y = 0;
  }
  return d;
}

long long mod_inverse(long long a, long long m){
  long long x,y;
  extgcd(a,m,x,y);
  return (m+x%m)%m;
}
template<typename T = long long>
class Number_Theoretic_Transform {
  // return the vector of F[t] or f[x] where
  // F[t] = sum of { f[x] * exp(-j * theta * t * x) } in x = 0 .. N-1 (FFT)
  // f(x) = 1/N * sum of { F[t] * exp(j * theta * t * x) } in t = 0 .. N-1 (inverse FFT)
  // where theta = 2*PI / N
  // N == 2^k
  // use the rotater as (primitive-root of mod) ^ t in NTT, which is used as exp(j*theta)^t in FFT

  //事前に計算した 単位回転子 rotater (MOD mod 上での位数が2^kとなる数) を 引数に与える。
  //逆変換のときには rotater^-1 (MOD mod) を rotaterに与える

  vector< T > do_NTT(vector< T > A, bool inverse){
    int N = A.size();

    //bit reverse
    for(int bit=0; bit<N; bit++){
      int rbit = 0;
      for(int i=0, tmp_bit = bit; i<k-1; i++, rbit <<= 1, tmp_bit >>=  1){
        rbit |= tmp_bit & 1;
      }
      rbit >>= 1;
      if(bit < rbit){
        swap(A[bit], A[rbit]);
      }
    }


    int dist = 1;
    vector<T>& theta = (inverse?inv_theta_v:theta_v);

    T t = k-1;
    T half = theta[k-1];  //半回転

    for(int level = 1; level<k; level++){
      T W_n = theta[t]; //rotater ^ theta (MOD mod)
      T W = 1;              //rotater
      for(int x=0; x < (1<<(level-1)); x++){
        for(int y=x; y+dist < N; y += (dist<<1)){
          T tmp = A[y+dist]*W;
          if(tmp >= mod) tmp %= mod;

          A[y+dist] = A[y] + (tmp*half) % mod;
          if(A[y+dist] >= mod) A[y+dist] %= mod;

          A[y] = A[y] + tmp;
          if(A[y] >= mod) A[y] %= mod;
        }
        W = W*W_n;
        if(W>=mod) W%=mod;
      }
      dist <<= 1;
      t -= 1;
    }

    if(inverse){
      for(int i=0; i<N; i++){
        A[i] = z * A[i];
        if(A[i] >= mod) A[i] %= mod;
      }
    }
    

    return A;
  }

public:
  const T mod;
  const T rotater;
  const T inv_rotater;
  const T k;
  vector<T> theta_v;
  vector<T> inv_theta_v;
  const T z;

  Number_Theoretic_Transform(T mod_, T rotater_, T k_) : 
    mod(mod_), 
    rotater(rotater_), 
    k(k_), 
    inv_rotater(mod_inverse(rotater_, mod)),
    z(mod_inverse(1<<(k-1), mod)) // 1/Nを掛けるところなので N^-1 MOD modを掛けたいところだけど何故か (N/2)^-1 で上手く行く……
  {

    theta_v = vector<T>(k+1,1);
    theta_v[0] = rotater;
    for(int i=1; i<=k; i++){
      theta_v[i] = theta_v[i-1] * theta_v[i-1];
      if(theta_v[i] >= mod) theta_v[i] %= mod;
    }

    inv_theta_v = vector<T>(k+1,1);
    inv_theta_v[0] = inv_rotater;
    for(int i=1; i<=k; i++){
      inv_theta_v[i] = inv_theta_v[i-1] * inv_theta_v[i-1];
      if(inv_theta_v[i] >= mod) inv_theta_v[i] %= mod;
    }
    
    

  };

  vector< T > NTT(vector< T > A){
    return do_NTT(A, false);
  }

  vector< T > INTT(vector< T > A){
    return do_NTT(A, true);
  }

  // vector<double> C | C[i] = ΣA[i]B[size-1-j]
  vector<T> convolution(vector<T> &A, vector<T> &B){
    int n = A.size();

    assert(A.size() == B.size());
    assert( n == (1<<k) );  //Nは2^kである必要がある(事前にresize)

    auto A_NTT = NTT(A);
    auto B_NTT = NTT(B);

    for(int i=0; i<n; i++){
      A_NTT[i] *= B_NTT[i];
      if(A_NTT[i] >= mod) A_NTT[i] %= mod;
    }
    return INTT(A_NTT);
  }
};


long long gcd(long long a, long long b){
  if(b==0) return a;
  return gcd(b, a%b);
}

long long lcm(long long a, long long b){
  if(a<b) swap(a,b);
  if(b==1) return a;
  return a * (b/gcd(a,b));
}

// Z % Yi = Xi であるようなZを求める。Garnerのアルゴリズム O(N^2)
// 参考 http://techtipshoge.blogspot.jp/2015/02/blog-post_15.html
// http://yukicoder.me/problems/448
long long Chinese_Remainder_Theorem_Garner(vector<long long> x, vector<long long> y, long long MOD){
  int N = x.size();

  bool valid = true;
  //前処理
  //gcd(Yi,Yj) == 1 (i!=j) でなくてはならないので、
  //共通の因数 g = gcd(Yi,Yj) を見つけたら片側に寄せてしまう
  for(int i=0; i<N; i++){
    for(int j=i+1; j<N; j++){
      if(i == j) continue;
      long long g = gcd(y[i], y[j]);

      if( x[i]%g != x[j]%g ) valid = false; //解が存在しない

      if(g != 1){
        y[i] /= g; y[j] /= g;
        long long g_ = gcd(y[i], g);
        while(g_ != 1){
          y[i] *= g_;
          g /= g_;
          g_ = gcd(y[i], g);
        }
        y[j] *= g;

        x[i] %= y[i];
        x[j] %= y[j];
      }
    }
  }

  if(!valid){
    cerr << -1 << endl;
    abort();
    return 0;
  }

  //Garner's algorithm
  vector<long long> z(N);
  for(int i=0; i<N; i++){
    z[i] = x[i];
    for(int j=0; j<i; j++){
      z[i] = mod_inverse(y[j], y[i]) % y[i] * (z[i] - z[j]) % y[i];

      z[i] = (z[i]+y[i])%y[i];
    }
  }

  long long ans = 0;
  long long tmp = 1;
  for(int i=0; i<N; i++){
    ans = (ans + z[i] * tmp)%MOD;

    tmp = (tmp * y[i])%MOD;
  }

  return ans;
}


struct trie_node{
  map<char, int> child;
  int count;
};
vector<trie_node> trie;

vector<long long> s[200001];
vector<long long> w[200001];

vector<long long> convolution(vector<long long> a, vector<long long> b){
  int x = a.size();
  int y = b.size();
  if(x*y <= (x+y)*30 ){
    vector<long long> res(x+y-1);
    for(int i=0; i<x; i++){
      for(int j=0; j<y; j++){
        (res[i+j] += a[i] * b[j] % mod) %= mod;
      }
    }
    return res;
  }

  long long k = 5;
  while( (1<<k) <= x+y ) k++;
  k++;
  assert( k < 16 );

  a.resize(1<<k, 0);
  b.resize(1<<k, 0);

  const static vector<vector<vector<int>>> param = {
    {
    },
    {
    },
    {
      {2, 17011, 4989209},
      {2, 7027, 4937873},
      {2, 12941, 4925573},
    },
    {
      {3, 13003, 4827737},
      {3, 9521, 4813577},
      {3, 10079, 4767457},
    },
    {
      {4, 15859, 4899329},
      {4, 4679, 4895057},
      {4, 15749, 4866209},
    },
    {
      {5, 15817, 4994401},
      {5, 15227, 4988449},
      {5, 14947, 4917217},
    },
    {
      {6, 8117, 4989121},
      {6, 17123, 4974913},
      {6, 761, 4952257},
    },
    {
      {7, 14551, 4928513},
      {7, 9781, 4832257},
      {7, 4663, 4797953},
    },
    {
      {8, 4973, 4933889},
      {8, 10159, 4856321},
      {8, 16229, 4817921},
    },
    {
      {9, 10567, 4996609},
      {9, 12143, 4996097},
      {9, 13249, 4984321},
    },
    {
      {10, 631, 4995073},
      {10, 15727, 4979713},
      {10, 14029, 4953089},
    },
    {
      {11, 3041, 4933633},
      {11, 4201, 4909057},
      {11, 569, 93493249},
    },
    {
      {12, 16633, 4902913},
      {12, 5527, 4845569},
      {12, 1009, 97497089},
    },
    {
      {13, 13687, 48701441},
      {13, 709, 4866049},
      {13, 163, 4816897},
    },
    {
      {14, 24763, 99500033},
      {14, 26249, 99106817},
      {14, 14867, 98992129},
    },
    {
      {15, 9859, 98861057},
      {15, 6427, 97615873},
      {15, 11393, 97320961},
    },
  };

  vector<long long> P;
  vector<long long> R;

  vector<vector<long long>> tmp(3);
  for(int i=0; i<3; i++){
    R.push_back( param[k][i][1] );
    P.push_back( param[k][i][2] );

    assert( mod_pow(R[i], 1<<k, P[i]) == 1  );
    assert( mod_pow(R[i], 1<<k-1, P[i]) != 1  );

    Number_Theoretic_Transform<long long> NTT(P[i], R[i], k);
    tmp[i] = NTT.convolution(a,b);
  }

  vector<long long> res(x+y-1);
  for(int i=0; i<res.size(); i++){
    vector<long long> w = {tmp[0][i], tmp[1][i], tmp[2][i]};
    res[i] = Chinese_Remainder_Theorem_Garner(w,P, mod);
  }

  return res;
}

// dp[i][x] := i 頂点で合計長 x の組み合わせ
void dfs(int pos, long long d){
  if( trie[pos].count == 1 ){
    w[pos].resize(2);
    s[pos].resize(2);
    w[pos][0] = w[pos][1] = 1;
    s[pos][0] = 0; s[pos][1] = 1;
    return;
  }
  if( trie[pos].child.size() == 1 ){
    int nx = trie[pos].child.begin()->second;
    dfs(nx, d+1);
    swap( s[pos], s[nx] );
    swap( w[pos], w[nx] );
    return;
  }

  w[pos].resize(1);
  s[pos].resize(1);
  w[pos][0] = 1;
  s[pos][0] = 0;

  for(auto p : trie[pos].child){
    if( p.first == '}' ) continue;

    int nx = p.second;
    dfs(nx, 1);

    for(int i=1; i<w[pos].size(); i++){
      w[pos][i] = w[pos][i] * fact_inv[i] % mod;
      s[pos][i] = s[pos][i] * fact_inv[i] % mod;
    }
    for(int i=1; i<w[nx].size(); i++){
      w[nx][i] = w[nx][i] * fact_inv[i] % mod;
      s[nx][i] = s[nx][i] * fact_inv[i] % mod;
    }

    auto w_ = convolution(w[pos], w[nx]);
    auto s1 = convolution(s[pos], w[nx]);
    auto s2 = convolution(w[pos], s[nx]);
    vector<long long> s_(w_.size());
    for(int i=0; i<s1.size(); i++){
      s_[i] = s1[i] + s2[i];
      s_[i] %= mod;
    }

    // for(int i=0; i<w[pos].size(); i++){
    //   for(int j=0; j<w[nx].size(); j++){
    //     if(i+j >= w_.size()){
    //       w_.resize(i+j+1);
    //       s_.resize(i+j+1);
    //     }
    //     (w_[i+j] += w[pos][i] * w[nx][j] % mod) %= mod;
    //     (s_[i+j] += s[pos][i] * w[nx][j] % mod) %= mod;
    //     (s_[i+j] += w[pos][i] * s[nx][j] % mod) %= mod;
    //   }
    // }

    swap(w[pos], w_);
    swap(s[pos], s_);

    for(int i=1; i<w[pos].size(); i++){
      w[pos][i] = w[pos][i] * fact[i] % mod;
      s[pos][i] = s[pos][i] * fact[i] % mod;
    }
  }

  w[pos].resize( trie[pos].count + 1 );
  s[pos].resize( trie[pos].count + 1 );
  for(long long i=1; i<w[pos].size(); i++){
    (s[pos][i] += d*min<long long>(i, trie[pos].count-1)%mod * w[pos][i]) %= mod;
  }
}

int main(){
  timer();

  fact[0] = fact_inv[0] = 1;
  for(int i=1; i<10001; i++){
    fact[i] = fact[i-1] * i % mod;
    fact_inv[i] = mod_pow(fact[i], mod-2, mod);
  }

  int n;
  cin >> n;
  vector<string> s_(n);
  for(int i=0; i<n; i++){
    cin >> s_[i];
    s_[i] += '{';
  }
  s_.push_back("}");

  trie.push_back( trie_node{{}, 0} );
  for(int i=0; i<s_.size(); i++){
    int pos = 0;
    for(int j=0; j<s_[i].size(); j++){
      trie[pos].count++;
      if( trie[pos].child.count( s_[i][j] ) == 0 ){
        trie.push_back( trie_node{ {}, 0} );
        trie[pos].child[s_[i][j]] = trie.size()-1;
      }
      pos = trie[pos].child[s_[i][j]];
    }
    trie[pos].count++;
  }

  dfs(0, 0);

  for(int i=1; i<=n; i++){
    ans[i-1] = s[0][i];
  }

  for(int i=0; i<n; i++) cout << ans[i]%mod << endl;

  timer();
  return 0;
}
0