1. 程式人生 > 實用技巧 >【題解】 CF1418G Three Occurrences hash+雙指標+差分

【題解】 CF1418G Three Occurrences hash+雙指標+差分

Legend

Link \(\textrm{to Codeforces}\)

給定長度為 \(n\ (1 \le n \le 5\times 10^5)\) 的序列,一個序列稱作是好的當且僅當序列中所有元素出現恰好 \(3\) 次。

請求出這個序列有多少個連續子序列是好的

Editorial

做資料結構時候做到的。。。以為是什麼神仙資料結構。。。結果看了下 \(\rm{tag:hash}\)

注意題目中的條件,數字出現恰好 \(3\) 次。可以把同樣的數字按順序給定 \(3\) 個雜湊值 \(v_1,v_2,v_3\),(每個數字的雜湊值不同)

並且滿足 \(v_1 \operatorname{xor} v_2 \operatorname{xor} v_3 = 0\)

這樣子做只要一個區間異或和為 \(0\) 就說明有很大概率說明所有數字出現的次數都是 \(3\) 的倍數。

我們只要通過雙指標卡著區間就可以確保每個數字出現次數都是 \(3\) 次以內。hash 加差分應該是個套路不贅述。

複雜度 \(O(n \log n)\)

Code

\(\rm{Codeforces}\),單雜湊會把你卡到生不如死。

所以還是老老實實寫雙雜湊吧!

#include <bits/stdc++.h>

#define ULL unsigned long long
#define mp make_pair
#define p pair<ULL ,ULL>

using namespace std;

map<p ,int> MAP;

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x;
}

const int MX = 5e5 + 233;
int app[MX] ,a[MX];
ULL rnd[3][MX] ,cnt[MX] ,ha[MX] ,ha2[MX];
ULL rnd2[3][MX];

ULL r(){
	return ((ULL)(rand()) << 31) ^ rand();
}

int main(){
	srand(19260817);
	int n = read();
	for(int i = 1 ; i < MX ; ++i){
		rnd[0][i] = r();
	   	rnd[1][i] = r();
		rnd[2][i] = rnd[0][i] ^ rnd[1][i];
		rnd2[0][i] = r();
	   	rnd2[1][i] = r();
		rnd2[2][i] = rnd2[0][i] ^ rnd2[1][i];	
	}

	int l = 1;
	ULL Ans = 0;
	MAP[mp(0ull ,0ull)] = 1; app[0] = 1;
	for(int i = 1 ; i <= n ; ++i){
		a[i] = read();
		ha[i] = ha[i - 1] ^ rnd[cnt[a[i]]][a[i]];
		ha2[i] = ha2[i - 1] ^ rnd2[cnt[a[i]]][a[i]];
		cnt[a[i]] = (cnt[a[i]] + 1) % 3;
		while(app[a[i]] == 3){
			--app[a[l]];
			--MAP[mp(ha[l - 1] ,ha2[l - 1])];
			++l;
		}
		++app[a[i]];
		Ans += MAP[mp(ha[i] ,ha2[i])];
		// fprintf(stderr ,"i = %d ,Ans = %llu\n" ,i ,Ans);
		++MAP[mp(ha[i] ,ha2[i])];
	}
	cout << Ans << endl;
	return 0;
}