[51Nod1814]Clarke and string(回文树)

Description

克拉克是一名人格分裂患者.有一天克拉克分裂成 n n 个人.
每个克拉克手里有一个由小写字母组成字符串ai.克拉克们还有 q q 次询问,第i次询问,克拉克们想知道有多少个回文串同时出现在 axi a x i ayi a y i 中.
一个字符串称为回文串当且仅当这个串前后反转后与这个串相同。

n,q,ai100000 n , q , ∑ a i ⩽ 100000

Solution

对于每组询问,暴力构出两个串的回文树,然后在较短的串中查询即可。为了加快速度,为每组询问加上记忆化。

然后就AC啦?!

让我们证明一下这样做的正确性,即时间复杂度为 O(nn) O ( n n )
设每组询问的串为 x x ,y

  1. |sx|n | s x | ⩽ n |sy|n | s y | ⩽ n ,由于时间复杂度与较短的串有关,所以复杂度显然正确。

  2. 否则 |sx|n | s x | ⩾ n 的串最多有 n n 个, |sx|n | s x | ⩾ n |sy|n | s y | ⩾ n 最多只有 n n <script type="math/tex" id="MathJax-Element-17">n</script>种询问,加上记忆化后显然正确。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 100005;
int n, q;
vector<char> s[maxn];

inline int gi()
{
    char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    int sum = 0;
    while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
    return sum;
}

map<pair<int, int>, int> f;

struct palindromic_tree 
{
    pair<int, int> ch[maxn][26];
    int cnt, tot, ans, len[maxn], fail[maxn], last;

    inline void clear()
    {
        last = 1; len[tot = 1] = -1; fail[0] = fail[1] = 1; ++cnt;
    }

    inline void add(int t, int c, int n)
    {
        int x = last;
        while(s[t][n - len[x] - 1] != s[t][n]) x = fail[x];
        if(ch[x][c].second != cnt) {
            int v = ++tot, k = fail[x];
            while(s[t][n - len[k] - 1] != s[t][n]) k = fail[k];
            fail[v] = ch[k][c].second == cnt ? ch[k][c].first : 0; len[v] = len[x] + 2; ch[x][c] = make_pair(v, cnt);
        }
        last = ch[x][c].first;
    }   
}t1, t2;

int l, r, que1[maxn], que2[maxn];
inline int bfs(int s)
{
    l = 0; r = 1; que1[r] = que2[r] = s;
    do {
        ++l; int u = que1[l], v = que2[l];
        for(int i = 0; i < 26; ++i)
            if(t1.ch[u][i].second == t1.cnt && t2.ch[v][i].second == t2.cnt) {
                ++r; que1[r] = t1.ch[u][i].first; que2[r] = t2.ch[v][i].first;
            }
    }while(l < r);
    return r - 1;
}

inline int solve(int x, int y)
{
    if(f.count(make_pair(x, y))) return f[make_pair(x, y)];
    if(s[x].size() > s[y].size()) swap(x, y);
    t1.clear(); t2.clear();
    register int n, i;
    for(n = s[x].size(), i = 0; i < n; ++i) t1.add(x, s[x][i] - 'a', i);
    for(n = s[y].size(), i = 0; i < n; ++i) t2.add(y, s[y][i] - 'a', i);
    return f[make_pair(x, y)] = bfs(0) + bfs(1);
}

int main()
{
    scanf("%d\n", &n);
    for(int i = 1; i <= n; ++i) {
        char c = getchar(); s[i].push_back(0);
        while('a' <= c && c <= 'z') s[i].push_back(c), c = getchar();
    }
    scanf("%d\n", &q);
    for(int lastans = 0, i = 1, x, y; i <= q; ++i) {
        x = gi() ^ lastans; y = gi() ^ lastans;
        printf("%d\n", lastans = solve(x, y));
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值