哈希乱搞:CF1418G Three Occurrences

这道题看起来并不是那么好做,看到题解神秘做法,记录下来。

考虑枚举右端点,统计符合条件的左端点数量。

发现 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;
}
posted @ 2025-10-16 14:46  BaiBaiShaFeng  阅读(4)  评论(0)    收藏  举报
Sakana Widget右下角定位