ABC295 D题 题解
阅读原文时间:2023年10月05日阅读:4

题意简述

给定一个长度不超过\(\;5\times 10^5\;\)的,仅有数字构成的字符串,问存在多少段子串,使得子串内字符重新排序后,前半段与后半段相同?

做法分析

重组后前后两部分相同,其实也就意味着,这一子串内所有数字出现的次数都为偶数次。

考虑暴力竹筏,枚举左端点和右端点,统计子串内每个数字出现次数,判断是否都为偶次。但是这样达到\(O(n^3)\)级别,效率过低。

可以利用前缀和的思想进行优化,对于每一位 i,统计从第一位到这一位每个数字 x 出现的次数 \(num_{i,x}\) ,而子串 \((L, R)\) 内数字 x 出现次数为偶次,等价于 \(num_{R, x}\) 减 \(num_{L-1, x}\) 为偶次,等价于 \(num_{R, x}\) 和 \(num_{L-1, x}\) 的奇偶性相同。

这时可以发现,其实出现了几次并不重要,重要的是出现次数的奇偶性,那就可以把每一位的状态值整合成一个二进制下,十位的,由 0 和 1 组成的数字。而出现的每个数字奇偶性相同等价于状态值相同。

那做法就很明了了,从前往后统计每一位的状态值,将答案加上这一状态值之前出现的次数,最后在将这一状态值出现次数加 1 。

参考代码

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

    string s;
    long long ans, now;
    map<long long, int> m;

    int main()
    {
        cin >> s;
        m[0] = 1; //注意:第零位,每种数字都没出现也要计算,因为从第一位到第 x 位实际上是要判断第 0 位和第 x 位的状态值
        for(int i = 0;i < s.size(); i++)
        {
            int x = s[i] - '0';
            now ^= 1 << x;
            ans += m[now];
            m[now]++;
        }

        cout << ans;

        return 0;
    }