洛谷P4065 [JXOI2017] 颜色
解题思路
具体做法
我们希望找到一个合理的区间判定方法。
一个很直观的思路就是,维护一个哈希函数,使得区间的值为 \(0\)。
换句话说,在本题中,我们要找到一种维护方法,使得每种颜色的所有数在哈希函数里总贡献为 \(0\)。
考虑异或哈希。给每个位置上的数随机赋一个权值 \(w_i\),如果它是同种颜色中的最后一个数,就让其权值等于同颜色的其它权值异或起来。
记这些 \(w_i\) 的前缀异或数组为 \(s_0,s_1,s_2,\dots,s_{n}\)。考虑 \(s\) 数组中任意相同的一对数,都能构成一个合法区间。直接统计答案即可。
当然这题的异或哈希也可以改用求和哈希。
正确率证明
考虑什么情况下会出错——必然是某个不合法的区间,它却异或起来为 \(0\)。
这样的单个区间错误概率为 \(\frac{1}{2^{60}}\)。
而区间总个数 \(\leq n^2\),总错误概率小于 \(\frac{n^2}{2^{60}}\)。
代码实现
点击查看代码
#include <bits/stdc++.h>
#define FL(i, a, b) for (int i = (a); i <= (b); ++i)
#define FR(i, a, b) for (int i = (a); i >= (b); --i)
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
constexpr int N = 3e5 + 10, V = 1e9;
mt19937_64 RandLL(chrono::steady_clock::now().time_since_epoch().count());
int n, a[N], cnt[N];
ull now[N], key[N], sum[N];
map<ull, int> mp;
void Solve() {
scanf("%d", &n);
fill(cnt, cnt + n + 1, 0);
FL(i, 1, n) {
scanf("%d", &a[i]);
++cnt[a[i]];
}
FL(i, 1, n) {
if (--cnt[a[i]]) {
key[i] = RandLL();
} else {
key[i] = now[a[i]];
}
now[a[i]] ^= key[i];
sum[i] = sum[i - 1] ^ key[i];
}
ll ans = 0;
mp.clear();
FL(i, 0, n) {
++mp[sum[i]];
}
for (auto x: mp) {
ans += (ll)x.se * (x.se - 1) / 2;
}
printf("%lld\n", ans);
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
Solve();
}
return 0;
}