20200807 练习:字符串

T1 P3193 [HNOI2008]GT考试

思路:
f i , j f_{i,j} fi,j 表示有 i i i 个号码,kmp 上匹配到第 j j j 位时的情况总数
g i , j g_{i,j} gi,j 表示 kmp 上第 i i i 个位置转移到第 j j j 个位置的总方案数
dp 方程:
f i , j = ∑ k = 0 m f i − 1 , k × g k , j f_{i,j}=\sum_{k=0}^{m} f_{i-1,k}\times g_{k,j} fi,j=k=0mfi1,k×gk,j
时间复杂度: O ( n ) O(n) O(n)
考虑优化
发现 f f f 的第一维是递推关系,可以矩阵快速幂
最后的答案就是 [ 1 , 0...0 ] [1,0...0] [1,0...0] 乘上转移矩阵的 n n n 次方
就是转移矩阵的 n n n 次方的第零行和

代码:

#include <bits/stdc++.h>
using namespace std;
#define re register
namespace IO {
  char _buf[1 << 21], *_p1 = _buf, *_p2 = _buf;
#define ch()                                                                 \
  (_p1 == _p2 &&                                                             \
   (_p2 = (_p1 = _buf) + fread(_buf, 1, 1 << 21, stdin), _p1 == _p2) \
   ? EOF                                                                 \
   : *_p1++)
  inline int in() {
    int s = 0, f = 1;
    char x = getchar();
    while (x < '0' || x > '9') {
      if (x == '-') f = -1;
      x = getchar();
    }
    while (x >= '0' && x <= '9') {
      s = (s * 10) + (x & 15);
      x = getchar();
    }
    return f = 1 ? s : -s;
  }
  char _buf_[1 << 21];
  int _p1_ = -1;
  inline void flush() {
    fwrite(_buf_, 1, _p1_ + 1, stdout);
    _p1_ = -1;
  }
  inline void pc(char x) {
    if (_p1_ == (1 << 21) - 1) flush();
    _buf_[++_p1_] = x;
  }
  inline void out(int x) {
    char k[20];
    int tot = 0;
    if (!x) {
      pc('0');
      return;
    }
    if (x < 0) {
      pc('-');
      x = -x;
    }
    while (x) {
      k[++tot] = (x % 10) | 48;
      x /= 10;
    }
    for (int i = tot; i; i--) pc(k[i]);
    return;
  }
}  // namespace IO
using namespace IO;

const int A = 50;
int n, m, mod;
char a[A];
int fail[A];
struct node {
  int s[A][A];
  inline void clean() {
    for (int i = 0; i < m; i++)
      for (int j = 0; j < m; j++) s[i][j] = 0;
    return;
  }
  inline friend node operator*(const node u, const node v) {
    node r;
    r.clean();
    for (int i = 0; i < m; i++)
      for (int j = 0; j < m; j++)
        for (int k = 0; k < m; k++)
          r.s[i][k] = (r.s[i][k] + u.s[i][j] * v.s[j][k] % mod) % mod;
    return r;
  }
} p;

inline void prepare() {
  for (int i = 1, j = 0; i <= m; i++) {
    while (j && a[i + 1] != a[j + 1]) j = fail[j];
    if (a[i + 1] == a[j + 1]) j++;
    fail[i + 1] = j;
  }
  for (int i = 0; i < m; i++) {
    for (int j = 0; j <= 9; j++) {
      int t = i;
      while (t && a[t + 1] != j + '0') t = fail[t];
      if (a[t + 1] == j + '0') t++;
      p.s[i][t]++;
    }
  }
  return;
}

inline node power(node u, int c) {
  node r;
  r.clean();
  for (int i = 0; i < m; i++) r.s[i][i] = 1;
  while (c) {
    if (c & 1) r = r * u;
    u = u * u;
    c >>= 1;
  }
  return r;
}

signed main() {
  n = in(), m = in(), mod = in();
  scanf("%s", a + 1);
  prepare();
  p = power(p, n);
  int res = 0;
  for (int i = 0; i < m; i++) res = (res + p.s[0][i]) % mod;
  out(res), pc('\n');
  flush();
  return 0;
}

T2 P3715 [BJOI2017]魔法咒语

上面那道题的加强版(加强了不止一个档次吧……

思路:
观察数据范围,分层做

对于前 60pts
对禁止串建 ACM,对于每一个点预处理出每个串转移后的点
然后 AC自动机dp 即可

对于后 40pts
先考虑基本词汇长度为 1 的情况
类似上一道题,得出 dp 方程:
f i , j = ∑ k = 0 t o t f i − 1 , k × g k , j f_{i,j}=\sum_{k=0}^{tot}f_{i-1,k}\times g_{k,j} fi,j=k=0totfi1,k×gk,j
于是可以矩阵快速幂优化

考虑毒瘤的长度为 1,2 的情况
对于一组 f i f_i fi 可以由 f i − 1 , f i − 2 f_{i-1},f_{i-2} fi1,fi2 转移
发现类似斐波那契数列的转移
类比斐波那契数列的矩阵
∣ f i , 0 f i , 1 . . . f i , t o t f i − 1 , 0 f i − 1 , 1 . . . f i − 1 , t o t ∣ ⇓ ∣ f i + 1 , 0 f i + 1 , 1 . . . f i + 1 , t o t f i , 0 f i , 1 . . . f i , t o t ∣ \left| \begin{matrix} f_{i,0} & f_{i,1} & ... & f_{i,tot} & f_{i-1,0} & f_{i-1,1} & ... & f_{i-1,tot}\\ \end{matrix} \right|\\ \Downarrow\\ \left| \begin{matrix} f_{i+1,0} & f_{i+1,1} & ... & f_{i+1,tot} &f_{i,0} & f_{i,1} & ... & f_{i,tot}\\ \end{matrix} \right|\\ fi,0fi,1...fi,totfi1,0fi1,1...fi1,totfi+1,0fi+1,1...fi+1,totfi,0fi,1...fi,tot
建出转移矩阵
在这里插入图片描述
手玩理解
然后矩阵快速幂即可

代码:

#include <bits/stdc++.h>
using namespace std;
#define re register
#define int long long
namespace IO {
char _buf[1 << 21], *_p1 = _buf, *_p2 = _buf;
#define ch()                                                                 \
  (_p1 == _p2 &&                                                             \
           (_p2 = (_p1 = _buf) + fread(_buf, 1, 1 << 21, stdin), _p1 == _p2) \
       ? EOF                                                                 \
       : *_p1++)
inline int in() {
  int s = 0, f = 1;
  char x = getchar();
  while (x < '0' || x > '9') {
    if (x == '-') f = -1;
    x = getchar();
  }
  while (x >= '0' && x <= '9') {
    s = (s * 10) + (x & 15);
    x = getchar();
  }
  return f = 1 ? s : -s;
}
char _buf_[1 << 21];
int _p1_ = -1;
inline void flush() {
  fwrite(_buf_, 1, _p1_ + 1, stdout);
  _p1_ = -1;
}
inline void pc(char x) {
  if (_p1_ == (1 << 21) - 1) flush();
  _buf_[++_p1_] = x;
}
inline void out(int x) {
  char k[20];
  int tot = 0;
  if (!x) {
    pc('0');
    return;
  }
  if (x < 0) {
    pc('-');
    x = -x;
  }
  while (x) {
    k[++tot] = (x % 10) | 48;
    x /= 10;
  }
  for (int i = tot; i; i--) pc(k[i]);
  return;
}
}  // namespace IO
using namespace IO;

const int A = 210;
const int mod = 1e9 + 7;
int n, m, L;

char a[55][A], b[A];
int s[A];
int tr[A][26], tot;
int tag[A], fail[A];
inline void add() {
  int len = strlen(b + 1);
  int p = 0;
  for (int i = 1; i <= len; i++) {
    if (!tr[p][b[i] - 'a']) tr[p][b[i] - 'a'] = ++tot;
    p = tr[p][b[i] - 'a'];
  }
  tag[p] = 1;
  return;
}
inline void build() {
  queue<int> q;
  int p = 0;
  for (int i = 0; i < 26; i++) {
    p = tr[0][i];
    if (!p) continue;
    q.push(p);
  }
  while (!q.empty()) {
    int x = q.front();
    q.pop();
    for (int i = 0; i < 26; i++) {
      p = tr[x][i];
      if (!p) {
        tr[x][i] = tr[fail[x]][i];
        continue;
      }
      q.push(p);
      fail[p] = tr[fail[x]][i];
    }
    if (tag[fail[x]]) tag[x] = 1;
  }
  return;
}

namespace Sub1 {
int to[A][A], f[A][A];
inline void prepare() {
  for (int i = 0; i <= tot; i++)
    for (int j = 1; j <= n; j++) {
      int p = i, len = strlen(a[j] + 1);
      for (int k = 1; k <= len; k++) {
        p = tr[p][a[j][k] - 'a'];
        if (tag[p]) {
          p = -1;
          break;
        }
      }
      to[i][j] = p;
    }
  return;
}
inline void DP() {
  f[0][0] = 1;
  for (int i = 0; i < L; i++)
    for (int j = 0; j <= tot; j++)
      for (int k = 1; k <= n; k++)
        if (to[j][k] != -1)
          f[i + s[k]][to[j][k]] = (f[i + s[k]][to[j][k]] + f[i][j]) % mod;
  return;
}
inline void work() {
  prepare();
  DP();
  int res = 0;
  for (int i = 0; i <= tot; i++) res = (res + f[L][i]) % mod;
  out(res), pc('\n');
  flush();
  exit(0);
}
}  // namespace Sub1

namespace Sub2 {
struct Mat {
  int a[A][A];
  inline void clean() {
    for (int i = 0; i < (tot + 1) * 2; i++)
      for (int j = 0; j < (tot + 1) * 2; j++) a[i][j] = 0;
    return;
  }
  inline friend Mat operator*(Mat u, Mat v) {
    Mat r;
    r.clean();
    for (int i = 0; i < (tot + 1) * 2; i++)
      for (int j = 0; j < (tot + 1) * 2; j++)
        for (int k = 0; k < (tot + 1) * 2; k++)
          r.a[i][j] = (r.a[i][j] + u.a[i][k] * v.a[k][j] % mod) % mod;
    return r;
  }
} mx;
inline void prepare() {
  for (int i = 0; i <= tot; i++) {
    for (int j = 1; j <= n; j++) {
      if (s[j] == 1) {
        int p = tr[i][a[j][1] - 'a'];
        if (!tag[p]) mx.a[i][p]++;
      } else {
        int p = tr[i][a[j][1] - 'a'];
        if (tag[p]) continue;
        p = tr[p][a[j][2] - 'a'];
        if (!tag[p]) mx.a[i + tot + 1][p]++;
      }
    }
  }
  for (int i = 0; i <= tot; i++) mx.a[i][i + tot + 1] = 1;
  return;
}
inline Mat power(Mat s, int c) {
  Mat r;
  r.clean();
  for (int i = 0; i < (tot + 1) * 2; i++) r.a[i][i] = 1;
  while (c) {
    if (c & 1) r = r * s;
    s = s * s;
    c >>= 1;
  }
  return r;
}
inline void work() {
  prepare();
  mx = power(mx, L);
  int res = 0;
  for (int i = 0; i <= tot; i++) res = (res + mx.a[0][i]) % mod;
  out(res), pc('\n');
  flush();
  exit(0);
}
}  // namespace Sub2

signed main() {
  n = in(), m = in(), L = in();
  for (int i = 1; i <= n; i++) {
    scanf("%s", a[i] + 1);
    s[i] = strlen(a[i] + 1);
  }
  for (int i = 1; i <= m; i++) {
    scanf("%s", b + 1);
    add();
  }
  build();
  if (L <= 100)
    Sub1::work();
  else
    Sub2::work();
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值