POI2006 PAL-Palindromes

  • 给出 \(n\) 个回文串 \(s_1\sim s_n\),将它们两两组合拼接得到 \(n^2\) 个新串。求这些新串中有多少回文串。

  • \(\sum\limits_{i=1}^n|s_i|\le 2\times 10^6\)

约定:

  • 记字符串 \(s\) 的反串为 \(s^R\),比如 \((\texttt{ab})^R=\texttt{ba}\)

  • \(pre(s,l)\) 为字符串 \(s\) 长度为 \(l\) 的前缀,\(suf(s,l)\) 为字符串 \(s\) 长度为 \(l\) 的后缀。

其实 \(s_1\sim s_n\) 不是回文串也可以做的,所以不管这个性质。

考虑哈希,定义字符串 \(s\) 的哈希函数 \(H(s)=\sum\limits_{i=1}^{|s|}(P^{|s|-i}\times \text{ASCII}(s_i))\),其中取 \(P=1145141\),函数的返回值类型为 unsigned long long,在计算过程中自然溢出,默认不会冲突。

用哈希求解回文串题目先预处理出正、反串的前缀哈希值。开两个桶 \(mp_{0/1,x}\) 表示正/反串哈希值为 \(x\)\(s\) 数量。然后我们将新串分为两类:

  • 拼接的两个字符串长度相同

    枚举位于前面的那个串 \(s_i\),显然贡献为 \(\sum\limits_{j=1}^n[s_j=s_i^R]\),即 \(mp_{1,s_i}\)

  • 拼接的两个字符串长度不同

    枚举较长的那个串 \(s_i\),用它去和较短的串拼接。又分为两类:

    • \(s_i\) 在前

      这种情况一定是 \(s_i\) 的一个非空真后缀回文,且该非空真后缀之前的非空真前缀与短串对称。枚举回文非空真后缀的位置 \(j\),用哈希判断,贡献为 \(mp_{1,H(pre(s_i,j-1))}\)

    • \(s_i\) 在后

      这种情况一定是 \(s_i\) 的一个非空真前缀回文,且该非空真前缀之后的非空真后缀与短串对称。枚举回文非空真前缀的位置 \(j\),用哈希判断,贡献为 \(mp_{0,H((suf(s_i,j+1))^R)}\)

    可以看到两种情况的讨论是类似的。有人可能会问 \(mp\) 是对全局维护的,上面用 \(mp\) 计算贡献会不会算上比 \(s_i\) 长的串。显然是不会的,因为上面使用 \(mp\) 时均访问了短串的哈希值的下标以统计个数,而长串的哈希值时不会和短串的哈希值冲突的。

使用 __gnu_pbds::gp_hash_table 维护桶,时间、空间复杂度均为 \(\mathcal{O}\left(\sum\limits_{i=1}^n |s_i|\right)\)

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#define ull unsigned long long
using namespace std; const int N = 2e6 + 5; const ull P = 1145141;
vector<ull> H[N][2]; int a[N], n; string s; ull ans, pw[N];// 0 正; 1 反
__gnu_pbds::gp_hash_table<ull, ull> mp[2];
ull Hash(int id, bool type, int l, int r) {
   if (!type) return H[id][0][r] - H[id][0][l - 1] * pw[r - l + 1];
   return H[id][1][l] - H[id][1][r + 1] * pw[r - l + 1];
}
signed main() {
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(0); cin >> n;
    for (int i = pw[0] = 1; i < N; ++i) pw[i] = pw[i - 1] * P;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i] >> s; s = ' ' + s;
        H[i][0].resize(a[i] + 5), H[i][1].resize(a[i] + 5);
        for (int j = 1; j <= a[i]; ++j) H[i][0][j] = H[i][0][j - 1] * P + s[j];
        for (int j = a[i]; j >= 1; --j) H[i][1][j] = H[i][1][j + 1] * P + s[j];
        ++mp[0][H[i][0][a[i]]], ++mp[1][H[i][1][1]];
    }
    for (int i = 1; i <= n; ++i) {
        ans += mp[1][H[i][0][a[i]]];
        for (int j = 1; j < a[i]; ++j)
            if (Hash(i, 0, 1, j) == Hash(i, 1, 1, j)) ans += mp[0][H[i][1][j + 1]];
        for (int j = 2; j <= a[i]; ++j)
            if (Hash(i, 0, j, a[i]) == Hash(i, 1, j, a[i]))
                ans += mp[1][H[i][0][j - 1]];
    }
    return cout << ans, 0;
}
posted @ 2023-09-14 15:51  lzyqwq  阅读(40)  评论(0)    收藏  举报