异或维护奇偶性

最近一下碰见两道思路一样的题目,我觉得有必要记录一下这两道题目的解法。

第一道:牛客2020寒假集训题day4子段异或

题目大意:输入一个数列a,你需要输出其中异或值为0的不同子段的数量。

第二道:计蒜客CTU Open Contest 2019 G

题目大意:给定一个字符串 s ,从中选定一个最长的子串,使得该子串的字符通过重新组合(任意顺序)可以成为一个回文串,输出这个子串的长度。

这两道乍一看好像关系不大,但是其实它们的解法真的算是大同小异。

首先我们要知道异或值为0是什么意思,异或值为0,通常说明对于一个数列里面所有不同数的数量都是偶数。

这就产生了一个比较有用的应用,判断哪个数只出现一次。有兴趣的话看一下leetcode的Single number那题

如果出现一次,那么异或值为它本身;两次,就自身异或变为0。(奇偶性)

回到前面说的两道题目。

这两道题基本上都要用到异或前缀的思想,可能第一题比较直接,第二题比较隐晦。

由于奇偶性可以用异或表示,区间L,R的奇偶性等于区间1,R异或区间1-(L-1)。 

所以这是个经典的解法:枚举以R为区间右端点,先求出1-R中间数据的奇偶关系,顺便记录每次情况出现的最早位置即L,如果当前情况有记录,即有最早出现的位置,则说明找到了一组情况。

第一题就是这种做法。

而第二题多了一个步骤,就是判断回文串是奇数个的情况。

我们可以枚举每个字符,令这个字符是奇数,其他是偶数的最早出现的位置。

如何记录字符出现情况?我们就用状态压缩的方法。

#include <bits/stdc++.h>
using namespace std;
int n,x,i,j,vis[1<<21],ans;
string s;
int main()
{
    cin>>n;
    getchar();
    getline(cin,s);
    memset(vis,-1,sizeof(vis));
    vis[0]=0;
    for (i=0,x=0;i<n;i++)
    {
        x^=(1<<(s[i]-97));
        if (vis[x]!=-1) ans=max(ans,i-vis[x]+1);
        for (j=0;j<21;j++)
        {
            int tmp=x^(1<<j);
            if (vis[tmp]!=-1) ans=max(ans,i-vis[tmp]+1);
        }
        if (vis[x]==-1) vis[x]=i+1;
    }
    cout<<ans<<endl;
    return 0;
}
第二题

其实奇偶性本身就能出很多题目,异或这个神奇操作也是,所以做题目还是得学会变通才行啊。

猜你喜欢

转载自www.cnblogs.com/Y-Knightqin/p/12375077.html