洛谷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;
}
posted @ 2025-04-19 11:10  徐子洋  阅读(21)  评论(0)    收藏  举报