最近一下碰见两道思路一样的题目,我觉得有必要记录一下这两道题目的解法。
第一道:牛客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; }
其实奇偶性本身就能出很多题目,异或这个神奇操作也是,所以做题目还是得学会变通才行啊。