最难的不是打败别人,是战胜自己。
题目链接
题目描述
给定一个长度为n的整数序列c,
c
i
c_i
ci表示序列c中的第i种颜色。
我们定义一个颜色序列只有在它只包含出现偶数次的颜色时才是合法的。
例如,序列{0,1,0,1}是合法的,因为颜色1和0都出现了2次,而2是一个偶数。 序列{0,1,0}是不合法的,因为颜色1只出现了1次,而且1不是偶数。
现在,你需要算出c有多少个连续的子序列是合法的颜色序列。
输入描述
第一行包含一个整数(1≤n≤10^6),即序列c的长度
第二行包含n个整数,第i个整数表示第i个颜色,ci(0≤ci≤20)
输出描述
打印一个整数作为答案
输入
3
1 1 1
输出
2
一开始以为是动态规划,先求连续子序列有2个元素的情况,再求4个,再求8个……但是队友说给的n是10^6,如果是n ^2的动态规划会超时,所以只能是n或者nlogn的算法。
这个题还是很难想的,需要用到异或的性质。
想要知道一段序列是否符合要求,得知道这一段序列里每个颜色出现了奇数次还是偶数次。因此我们想象有一个有20位的数表,每一位表示一种颜色。
c20 | c19 | c18 | c17 | …… | c2 | c1 |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
怎么实现这个数表?可以用
2
c
i
2^{c_i}
2ci来表示每种颜色,第一种颜色就是
2
0
=
1
2^0=1
20=1,第二种颜色是
2
1
=
10
2^1=10
21=10,第三种颜色是
2
2
=
100
2^2=100
22=100,第四种颜色是
2
3
=
1000
2^3=1000
23=1000……这样的话不同的颜色就是不同位置上的1。因此每输入一个颜色,我们对它作位运算1<<
c
i
c_i
ci,把它变为二进制形式。这样最大是
2
19
=
524288
2^{19}=524288
219=524288
我们现在希望能够用每一位上的数字来表示这个颜色的状态。因为一种颜色我们不关心它具体出现了几次,只关心它出现了奇数次还是偶数次,所以可以用0和1来表示。如果一个颜色出现了一次,就让对应位变成1,如果它出现两次,就把对应位变为0,出现三次变为1……怎样实现这种操作呢?异或可以实现,1^1=0,0 ^1=1,用异或前缀和xorsum[i]数组表示前i个位置上所有颜色的状态,每输入进一个颜色,就转成二进制,然后和前i-1个位置的状态异或。
如序列为 0 2 2 1 3 3 0 1
x
o
r
s
u
m
[
1
]
=
x
o
r
s
u
m
[
0
]
xorsum[1]=xorsum[0]
xorsum[1]=xorsum[0]^
2
0
=
1
2^0=1
20=1(第0种颜色有奇数个)
x
o
r
s
u
m
[
2
]
=
x
o
r
s
u
m
[
1
]
xorsum[2]=xorsum[1]
xorsum[2]=xorsum[1]^
2
2
=
001
2^2=001
22=001 ^
100
=
101
100=101
100=101(0种和2种颜色有奇数个)
x
o
r
s
u
m
[
3
]
=
x
o
r
s
u
m
[
2
]
xorsum[3]=xorsum[2]
xorsum[3]=xorsum[2]^
2
2
=
101
2^2=101
22=101 ^
100
=
001
100=001
100=001(0种颜色有奇数个,2种颜色有偶数个)
x
o
r
s
u
m
[
4
]
=
x
o
r
s
u
m
[
3
]
xorsum[4]=xorsum[3]
xorsum[4]=xorsum[3]^
2
1
=
001
2^1=001
21=001 ^
010
=
011
010=011
010=011(0和1种颜色有奇数个,2种颜色有偶数个)
……
然后我们就能求出从开头到每个位置的状态。在这些状态里可以知道值为0的状态是符合要求的。
那么除了这种连续子序列,肯定还有那种起点不在左端点符合要求的子序列,怎么找呢
假设有两个位置的异或前缀和是相等的,这就说明它们之间出现了符合要求的偶数子序列,把不相等的部分异或掉了。比如上面例子里的1和3位置,之所以相等是因为2出现了两次,因此可以通过找相等的异或前缀和来找中间的连续子序列。
#include<bits/stdc++.h>
using namespace std;
int xorsum[1050000];//2^19=524288,异或最大能得到的情况是20位都是1,比2^20小,2^20=1048576,可以开1050000大小的数组
map<int,int> mp;
int main(){
int n,i;
cin>>n;
for(i=1;i<=n;i++){
int a;
cin>>a;
xorsum[i]=xorsum[i-1]^(1<<a);//2的a次方
}
mp[0]=1;//结果为0的异或前缀和本身是满足条件的
int ans=0;
for(i=1;i<=n;i++){
ans+=mp[xorsum[i]]; //对于每个位置,如果它是0一开始ans就+1
mp[xorsum[i]]++;//这个位置的异或前缀和结果+1,如果后面找到了这个结果,在上一步会直接把这些和它相等的都加上
}
cout<<ans<<endl;
return 0;
}