题意:
求长度为 n 的数列中,有多少连续的子数列,其中元素的出现次数都为偶数?
数列长度 n ≤ 1e6
,元素种类 m ≤ 20
。
思路:
数列长度在1e6,所以时间复杂度要控制在 n 或者 nlogn。
求满足条件的子串个数,要在O(n)
的复杂内,之前遇到过:
求和为7的倍数的字串个数、求01数量相等的子串个数。
其做法相同,map标记边走边处理,看当前的值之前是否出现过,如果是,那么加上前面出现的次数,次数++。 这道题同样!
用一个set,从前往后走,始终保证set中的元素始终都出现奇数次,看当前set是否在前面出现过。如果出现过,那么上次出现的位置到当前位置这一段中的所有元素都出现了偶数次。
-
如果当前的元素出现了奇数个,那么把当前元素放到set,判断前面是否出现。
-
如果当前元素出现了偶数个,那么就要把set中的当前元素去掉。判断前面是否出现过。
-
就像那个求01数量相等的子串那道题,碰见1,sum++;碰见0,sum–。如果当前sum之前出现过的话,那么上次出现的位置到当前位置这一段中的0和1出现次数相同。
是求子串个数,所以可以用map映射set,表示当前set出现了多少次。后面遍历到前面出现过,就加上映射值,即前面出现的次数。也就是以当前位置结尾的,满足条件的字串个数。从前往后走,累加,便能求出所有满足子串的个数。
但是这道题还有一个问题就是,如果用map映射set的话,内存超限!
可以用二进制来表示,如果种类 i
出现奇数次了,那么把二进制上的第i
为变为1,标记这个二进制所表示的数。如果种类i出现偶数次,那么就把二进制上的第j位变为0,判断这个二进制所表示的数是否出现过。
种类最多为20,所以二进制表示的数最大在 2 20 2^{20} 220,可以接受。
Code:
//keeping hurry and coding calm.
typedef pair <int, int> PII;
//map <set<int>, int> mp;
map <int,int> mp;
const int N = 30, mod = 1e9 + 7;
stack<int> stk;
vector<int> v;
set<int> st;
int T, n, m;
int cnt[N];
//二进制优化做法:
int main(){
cin>>n;
int t=0,ans=0;
mp[t]=1;
for(int i=1;i<=n;i++)
{
int x;cin>>x;
cnt[x]++;
if(cnt[x]%2==1)
{
t |= 1<<x;
ans += mp[t];
mp[t]++;
}
else
{
t &= ~(1<<x);
ans += mp[t];
mp[t]++;
}
}
cout<<ans;
return 0;
}
// map映射做法,内存超限
int main(){
cin>>n;
int ans=0;
mp[st]=1;
for(int i=1;i<=n;i++)
{
int x;cin>>x;
cnt[x]++;
if(cnt[x]%2)
{
st.insert(x);
ans+=mp[st];
mp[st]++;
}
else
{
st.erase(x);
ans+=mp[st];
mp[st]++;
}
}
cout<<ans;
return 0;
}
所以说,这道题其实就是一个前缀和+二进制,但是这道题的前缀和处理方式确实不好想。
全为奇数经过一段位置之后,还是变为全为奇数,那么中间这一段便都是偶数。
最后还不是常规的map映射,而是用二进制来优化。
挺不错的一道题!