洛谷P3501 ANT-Antisymmetry

文章介绍了如何解决编程竞赛中的一个问题,即给定一个01字符串,找出所有反对称子串的数量。方法涉及回文串的检测,使用哈希和二分查找优化时间复杂度至O(nlog_2n)。代码示例中展示了如何计算和扩展回文串,并累加子串数量。
摘要由CSDN通过智能技术生成

LuoguP3501

题目大意

对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。

现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。 N ≤ 5 × 1 0 5 N \le 5 \times 10^5 N5×105

思路

  • 考虑如何表示反对称,将一个01串按位取反后,如果和原串回文,则该串是反对称的。
    例如 01 01 01取反后是 10 10 10 0110 0110 0110回文的,所以 10 10 10是反对称的。

  • 接着考虑如何找回文,可以用哈希 O ( n ) O(n) O(n)处理, O ( 1 ) O(1) O(1)查询。
    但是题目问的是子串数量,如果枚举长度和起点,那么时间复杂度是 O ( n 2 ) O(n^2) O(n2)级别的,会超时。

  • 可以用中心扩展法,以每个字符为中心,左右扩展,而且扩展中心字符的左右求回文串的长度具有单调性(因为回文串的子串一定也是回文的),所以可以用二分,优化到 O ( n   l o g 2 n ) O(n\ log_2n) O(n log2n)

代码

注意一些细节:

  • 本题的回文串一定是偶数,如果是奇数,那么取反后一定不符合要求。
  • 二分判断回文,以 s [ x ] s[x] s[x]为中心, m i d mid mid为回文半径,判断 s s s [ x − m i d + 1 , x ] [x - mid + 1, x] [xmid+1,x] t t t [ x + 1 , x + m i d ] [x + 1, x +mid] [x+1,x+mid]是否回文( t t t s s s取反后的字符串)。
  • 二分找到最大回文半径 l l l后,则以 s [ x ] s[x] s[x]为中心的回文串的子串数量为 l l l,累加求和即可。
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;

const int Maxn = 5e5;
int n, PP = 131, ans;
string s, t;
ull P[Maxn + 3], f[Maxn + 3], g[Maxn + 3];

ull get_hash(int l, int r) {
    return f[r] - f[l - 1] * P[r - l + 1];
}

ull get_hash1(int l, int r) {
    return g[l] - g[r + 1] * P[r - l + 1];
}

void calc(int x) {
    int l = 0, r = min(x, n - x);
    while (l < r) {
        int mid = (l + r + 1) >> 1;
        if (get_hash(x - mid + 1, x) == get_hash1(x + 1, x + mid)) l = mid;
        else r = mid - 1;
    }

    ans += l;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);

    cin >> n >> s;
    s.insert(0, " ");
    t = s;
    for (int i = 1; i <= n; ++i)
        if (s[i] == '1') t[i] = '0';
        else t[i] = '1';

    P[0] = 1;
    for (int i = 1; i <= n; ++i) P[i] = P[i - 1] * PP;
    for (int i = 1; i <= n; ++i) f[i] = f[i - 1] * PP + s[i];
    for (int i = n; i >= 1; --i) g[i] = g[i + 1] * PP + t[i];
    for (int i = 1; i <= n; ++i) calc(i);

    cout << ans << '\n';
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值