题意:给一个数列,若在一个区间中出现过的数都出现了奇数次,则这个区间会被统计进入答案,问会有多少个区间被统计进答案;
n<=30000;
这个题的正解其实是从一个简单版问题中进化来的,如果是偶数次我们可以上一个神乎其技的随机算法,给每个数一个随机权值;
满足偶数次当且仅当区间异或和为0,数学分析可以发现这样出错的概率是极低的。奇数的怎么做呢,我们可以强制让一个数
在这个区间的第一次出现不被异或,这样就变成偶数次了。然后我们可以上一个分块来维护和统计,因为我们可以先算出前缀异或和
然后我们要做的只有两件事,统计值为0的数的数量,给所有的数异或上一个数,这里一开始我想错了,我想的是
先算出前缀异或和之后每删掉一个数就把他后面的前缀都异或一下这个数,但实际上应该是他的下一次出现位置及以后的前缀,
因为要删这个数时他已经是后面所有区间中这个数的第一次出现了,没有被异或进去,删掉后在i到next[i]中这个数都没出现过,当然也不能被
异或进去。注意代码实现时map的一个小技巧(好像就是常数除了2再加上一个clear,看来map[][]这种操作常数真的巨大,而clear就比一个一个删好得多),一开始我一直超时。
#include<iostream> #include<cmath> #include<cstdio> #include<cstring> #include<algorithm> #include<map> using namespace std; typedef unsigned long long ull; ull mar[1000010],t[30010],w[180]; int ans,cnt,now,bo,n,a[30010],pos[30010],vis[1000010],nex[30010]; map<ull,int> num[180]; void update(int po){ int l=pos[po]; ull tmp=mar[a[po]]; int lol=min(l*bo,n); for(register int i=po;i<=lol;++i){ t[i]^=tmp; } num[l].clear();//这个函数很快; //开始时我写的先把之前的num[i][t[i]]--,再把新的num[i][t[i]]++;就一直T; int ls=(l-1)*bo+1; for(register int i=ls;i<=lol;++i)num[l][t[i]]++; for(register int i=l+1;i<=cnt;++i)w[i]^=tmp; } inline int qs(int po){ int l=pos[po],res=0; int lol=min(l*bo,n); for(register int i=po;i<=lol;++i){ if(!(t[i]^w[l]))res++; } for(register int i=l+1;i<=cnt;++i){ if(num[i].count(w[i])) res+=num[i][w[i]]; } return res; } int main(){ for(register int i=1;i<=1000000;++i){ for(register int j=1;j<=3;++j){ mar[i]<<=16; mar[i]|=rand(); } } scanf("%d",&n); bo=(int)sqrt(n); for(register int i=1;i<=n;++i)scanf("%d",&a[i]); for(register int i=1;i<=n;++i){pos[i]=(i-1)/bo+1;cnt=max(cnt,pos[i]);} for(register int i=1;i<=n;++i){ if(!vis[a[i]]){t[i]=t[i-1];} else{ nex[vis[a[i]]]=i; t[i]=t[i-1]^mar[a[i]]; } vis[a[i]]=i; } for(register int i=1;i<=n;++i){ num[pos[i]][t[i]]++; } for(register int i=1;i<=n;++i){ ans+=qs(i); if(nex[i])update(nex[i]); } printf("%d",ans); system("pause"); return 0; } /* 4 2 2 2 3 */