这道题看起来并不是那么好做,看到题解神秘做法,记录下来。
考虑枚举右端点,统计符合条件的左端点数量。
发现 3 这个数字很小,发现区间中的数我们仅仅需要知道它 %3 的值。
我们如果可以记录一个位置前缀中所有值的出现情况就好了,但是明显不现实,整个数据是 \(n^2\) 级别的。
就算我们搞一棵主席树,似乎也没有什么可以快速确定左端点的方式。
如果我们可以把一大堆数的出现次数压成一坨,并且这一坨还可差分就好了。
想到了集合哈希。
我们给每一个数一个随机值,每个位置的哈希值就是出现次数 %3 乘上随机值。
不难发现两个位置如果哈希值是相等的,那么区间就是合法的。
但是这个办法有一个问题:没有办法保证恰好出现了 3 次。
我们考虑双指针,每次向右移动右端点,维护左端点,显然左端点只会向右。
就没有了↓
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;
const int MN=1e6+116;
int n, a[MN], cnt[MN], ans=0;
unordered_map <int, ull> trans;
map <ull, int> mp;
ull hashed[MN];
signed main(){mt19937_64 rnd(114514);ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);cin>>n; for(int i=1; i<=n; ++i) trans[i]=rnd();for(int i=1; i<=n; ++i){hashed[i]=hashed[i-1]; cin>>a[i];hashed[i]-=cnt[a[i]]*trans[a[i]];++cnt[a[i]]; cnt[a[i]]%=3;hashed[i]+=cnt[a[i]]*trans[a[i]];}mp[0]=1; memset(cnt,0,sizeof(cnt));for(int i=1,j=0; i<=n; ++i){++cnt[a[i]];while(cnt[a[i]]>3){--cnt[a[j]];if(j) --mp[hashed[j-1]];++j;}ans+=mp[hashed[i]];++mp[hashed[i]];}cout<<ans<<'\n';return 0;
}