「NOI2014」动物园

知识点:KMP
原题面:LojLuogu

简述

\(n\) 组数据,每次给定一字符串 \(s\)
定义 \(\operatorname{num}_i\) 表示是 \(s[1:i]\) 的前后缀,且长度不大于 \(\left\lfloor\frac{i}{2}\right\rfloor\) 的字符串的个数。
求:

\[\prod_{i=1}^{n}\left(\operatorname{num}_i + 1\right)\pmod {10^9 + 7} \]

\(1\le n\le 5\)\(1\le |s|\le 10^6\)
1S,512MB。

分析

做法是自己 YY 的,效率被爆踩但是能过(
\(\mathbf{B}(i)\) 表示满足既是前缀 \(s[1:i]\) 的真前缀,又是其真后缀的字符串组成的集合。

先不考虑长度不大于 \(\left\lfloor\frac{i}{2}\right\rfloor\) 这一限制,对于前缀 \(s[1:i]\),显然 \(\operatorname{num}_i\) 的值为 \(|\mathbf{B}(i)|\)。则显然有 \(\operatorname{num}_{i} = \operatorname{num}_{\operatorname{fail}_i} + 1\),表示在 \(\operatorname{num}_i\) 的基础上计入 \(s[1:i]\)\(\operatorname{border}\) 的贡献。\(\operatorname{num}\) 可在 KMP 算法中顺便求得。
再考虑限制,若前缀 \(s[1:i]\)\(\operatorname{border}\) 的长度大于 \(\left\lfloor\frac{i}{2}\right\rfloor\),则需要不断跳 \(\operatorname{fail}\),跳到第一个满足长度合法的位置 \(j\in \mathbf{B}(i)\),再统计其贡献 \(\operatorname{num}_j\)
暴跳实现可以获得 50pts 的好成绩。

发现跳 \(\operatorname{fail}\) 过程中对应的字符串长度会缩短(废话),考虑倒序枚举各位置 \(i\),使得 \(\left\lfloor\frac{i}{2}\right\rfloor\) 也呈现递减的状态。
考虑暴跳过程,显然是由于某些 \(\operatorname{fail}\) 的转移被重复统计,导致暴跳效率较低。考虑并查集的思路,将重复的转移进行路径压缩。
\(\operatorname{pos}_{i}\) 表示前缀 \(s[1:i]\) 在跳 \(\operatorname{fail}\) 之后对应的最大的第一个满足长度合法的 \(\mathbf{B}\) 中的元素,初始值为 \(\operatorname{pos}_i = i\)。在暴力跳 \(\operatorname{fail}\) 时,更新沿途遍历到的 \(\operatorname{pos}\) 即可。

这个路径压缩的复杂度我并不会证,但是感觉跑的还蛮快的= =

代码

//知识点:KMP
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e6 + 10;
const int mod = 1e9 + 7;
//=============================================================
int n, ans, next[kN], num[kN], pos[kN];
char s[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Init() {
  ans = 1;
  scanf("%s", s + 1);
  n = strlen(s + 1);
}
void KMP() {
  for (int i = 2, j = 0; i <= n; ++ i) {
    while (j > 0 && s[i] != s[j + 1]) j = next[j];
    if (s[i] == s[j + 1]) ++ j;
    next[i] = j;
    if (! j) continue ;
    pos[j] = j; //初始化
    num[j] = 1ll * (num[next[j]] + 1ll) % mod;
  }
}
int Find(int x_, int lth_) {
  if (pos[x_] <= lth_ / 2) return pos[x_];
  return pos[x_] = Find(next[pos[x_]], lth_); //路径压缩
}
//=============================================================
int main() {
  int t = read();
  while (t --) {
    Init(); KMP();
    for (int i = n; i >= 2; -- i) {
      pos[next[i]] = Find(next[i], i); //找到贡献位置
      if (! pos[next[i]]) continue ; //特判无贡献情况
      ans = 1ll * ans * (num[pos[next[i]]] + 1) % mod;
    }
    printf("%d\n", ans); 
  }
  return 0;
}
posted @ 2021-01-08 10:25  Luckyblock  阅读(162)  评论(0编辑  收藏  举报