【border树】【P2375】动物园

Description

给定一个字符串 \(S\),对每个前缀求长度不超过该前缀一半的公共前后缀个数。

共有 \(T\) 组数据,每组数据的输出是 \(O(1)\) 的。

Limitations

\(1 \leq |S| \leq 10^6,~1 \leq T \leq 5\)

Solution

好水的NOI题

建出 \(border\) 树,对于树上每个节点,它的所有 \(border\) 与它的所有祖先一一对应。这是因为每个节点的父亲是它的最长 \(border\),数学归纳可以证明这个结论。

考虑对于这棵 \(border\) 树上任何一个节点到根的路径上节点的编号一定是单调减小的,这是因为每个结点的最长 \(border\) 显然小于自身。

因此,对于一个点 \(u\),如果 \(v\) 是一个合法的 \(border\),那么 \(v\) 的祖先一定是合法的 \(border\)。所以合法的 \(border\) 一定是一条以根为一个端点的链。

综合上面两条可以得出,如果 \(u\) 的合法 \(border\) 对应链的另一个端点为 \(v\),那么 \(u\) 的孩子的合法 \(border\) 的对应端点一定在 \(v\)\(u\) 的路径上,因此这个端点位置是单调变深的。

所以只需要在 dfs 的时候维护根节点到该节点的链,并维护当前节点的答案位置即可。由于答案位置只会变大,因此答案位置的寻找是均摊 \(O(1)\) 的,暴力往下循环即可。

总时间复杂度 \(O(T |S|)\)

Code

#include <cstdio>
#include <cstring>
#include <vector>

const int maxn = 1000006;
const int MOD = 1000000007;

int T;
int n, top;
ll ans;
char MU[maxn];
int border[maxn], pos[maxn], stack[maxn];
std::vector<int>son[maxn];

void KMP();
void clear();
int ReadStr(char *p);
void dfs(const int u);

int main() {
  freopen("1.in", "r", stdin);
  qr(T);
  while (T--) {
    clear();
    n = ReadStr(MU + 1);
    KMP();
    for (int i = 1; i <= n; ++i) {
      son[border[i]].push_back(i);
    }
    dfs(0);
    qw(ans, '\n', true);
  }
  return 0;
}

int ReadStr(char *p) {
  auto beg = p;
  do *p = IPT::GetChar(); while ((*p > 'z') || (*p < 'a'));
  do *(++p) = IPT::GetChar(); while ((*p >= 'a') && (*p <= 'z'));
  *p = 0;
  return p - beg;
}

void KMP() {
  for (int i = 2, j = 0; i <= n; ++i) {
    while (j && (MU[i] != MU[j + 1])) j = border[j];
    if (MU[i] == MU[j + 1]) ++j;
    border[i] = j;
  }
}

void clear() {
  ans = 1; top = -1;
  for (auto &u : son) {
    u.clear();
  }
  memset(pos, 0, sizeof pos);
}

void dfs(const int u) {
  stack[++top] = u;
  for (auto v : son[u]) {
    int dv = v >> 1;
    int &_ans = pos[v];
    _ans = pos[u];
    while ((_ans < top) && (stack[_ans + 1] <= dv)) ++_ans;
    dfs(v);
  }
  if (pos[u]) {
    (ans += pos[u] * ans) %= MOD;
  }
  --top;
}

转载于:https://www.cnblogs.com/yifusuyi/p/11484142.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值