多校联合集训 A. 字符串“水”题 (状压+哈希)

题目描述

给出一个长度为 n 的字符串(1<=n<=100000),求有多少个连续字串中所有的字母都出现了偶数次。

 

输入

第一行一个正整数 T,表示数据组数(1 <= T <= 10)。

接下来 T 行,每行有一个只包含小写字母的字符串。

 

输出

每个答案输出满足要求字符串个数。每个答案占一行。

 

样例输入

3
a
aabbcc
abcabc

 

样例输出

0
6
1

 

思路

某位学长写的题解,感觉挺详细的,所以转过来了。

从左到右扫一遍字符串,利用状态压缩记录下截止到当前位置各字母是奇数还是偶数,若奇数,则为 1 ,否则为 0

试想,如果扫一个字符串时,截止到某种状态所有个数为奇数的字母与截止到之前某种状态所有个数为奇数的字母完全相同,则这两个状态之间的子串所有的字母一定都是出现了偶数次。(奇-奇=偶)

因为奇数用1表示,且所有个数为奇数的字母相同,(eg: 0001000000010000000100000100010000000100000001000001 ),那么这两个状态压缩之后一定相同。

如果扫到的状态之前出现过,则 ans+=该状态之前出现过的状态数

eg: ababab ,扫到第一个字母 a 时,状态为 100000000000000000000000001 ),当扫到第 5 个字母 a 时,状态又为 1 ,则两状态之间的字母一定都是出现了偶数次。

对于状态为 0 的情况,即截止到当前状态,所有字母都出现了偶数次,则其与最初始(尚未开始扫字符串)的状态相同,可与最初始的状态构成符合要求的连续子串,所以 status[0]=1

eg: abababab ,扫到第四个字母,状态为 0ans+=status[0] ,然后 ans=1 。截止到第四个字母的子串与截止到第 0 个字母的子串形成的连续子串,即 abab ,所有的字母都出现了偶数次。 ++status[0] (状态为 0 的状态数又多了一个,应加 1 )。

扫到第八个字母,状态为 0ans+=status[0] ,然后 ans=3 。截止到第八个字母的子串分别与截止到第 0 个字母的子串和截止到第四个字母的子串 形成的连续子串,即 abab ,所有的字母都出现了偶数次。

PS: status 开数组的话大约 6000 多万,清空会超时,因此改用 map

PS: 比赛开始的时候这道题直接用 map 是可以过的,不过后来数据加强了,应该也没有重判,所以原来的代码提交都会超时。

解决的办法是利用哈希来缩短每次查找的时间,之前学长们的做法都是打算用哈希链来处理的,不过我想到了用 map 数组处理。(突然想到了一个很好用并且很好写的解决哈希冲突的方法,感觉好开心~)

 

AC 代码

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <set>
#include <queue>
#include <stack>
#include <map>
#include <bitset>
#define maxn 100007
using namespace std;

typedef long long LL;
char str[110000];
map<int,int>sk[maxn];

void solve()
{
    memset(sk,0,sizeof(sk));
    for(int i=0; i<maxn; i++)
        sk[i].clear();
    int len=strlen(str);
    int ret=0;
    LL ans=0;
    sk[0][0]=1;
    for(int i=0; i<len; i++)
    {
        int now=str[i]-'a';
        ret^=(1<<now);
        ans+=sk[ret%maxn][ret];
        sk[ret%maxn][ret]++;
    }
    cout<<ans<<endl;
}

int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        cin>>str;
        solve();
    }
    return 0;
}