洛谷 P4065 题解
模拟赛 T1,纪念一下第一次场切紫。(话说场上这么多人切是不是都找到原了,就我这么傻想了半天)
正难则反,很容易的将题目转化为选择若干种颜色,使这些颜色在原数组中的位置连续。
设 $pre_i$ 为颜色 $i$ 最早出现的位置,$suf_i$ 为颜色 $i$ 最晚出现的位置。假设通过选择若干颜色得到的位置连续的区间为 $[l,r]$,充要条件为:
$\forall i$ $\epsilon$ $[l,r]$ $pre_i \geq l$
$\forall i$ $\epsilon$ $[l,r]$ $suf_i \leq r$
此结论可以感性理解,不想证了。
枚举一个右端点,(左端点也可以)发现 $suf_i \leq r$,$r$ 为一个定值,于是可以先二分出最小的 $l$,这里还要预处理 ``st 表``。第二个条件 $pre_i \geq l$ 就有点棘手。
其实也很简单,此时就需要用到线段树了,将区间 $[pre_i+1,i]$ 覆盖为 $0$ 即可,意思就是选了 $i$ 必须要向左延伸到 $pre_i$,否则构不成联通块。
然后就是查询,区间为二分出的 $l$ 和当前枚举的右端点 $r$。
本地跑了 $700ms$,时限 $200ms$ 居然能过就离谱了。
#include <bits/stdc++.h> #define For(i, a, b) for (int i = (a); i <= (b); i++) #define foR(i, a, b) for (int i = (a); i >= (b); i--) using namespace std; int n, x, y; int aa[19]; int lg[300005], f[19][300005]; // ST 表 int c[300005]; //颜色 int pre[300005], suf[300005]; //判断一个区间是否合法 int sum[1200005]; bool cover[1200005]; //线段树 inline int read() { char ch = getchar(); int x = 0; while (ch < '0' || ch > '9') ch = getchar(); while (ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); } return x; } int qmax(int x, int y) { int l = lg[y - x + 1]; return max(f[l][x], f[l][y - aa[l] + 1]); } void build(int l, int r, int k) { if (l == r) { sum[k] = 1; cover[k] = 0; return; } cover[k] = 0; int mid = l + r >> 1; build(l, mid, k << 1); build(mid + 1, r, k << 1 | 1); sum[k] = sum[k << 1] + sum[k << 1 | 1]; } void pushdown(int l, int r, int k) { if (cover[k]) { cover[k << 1] = cover[k << 1 | 1] = 1; sum[k << 1] = sum[k << 1 | 1] = 0; } } void update(int l, int r, int k) { //区间覆盖为 0 if (x <= l && y >= r) { cover[k] = 1; sum[k] = 0; return; } pushdown(l, r, k); int mid = l + r >> 1; if (x <= mid) update(l, mid, k << 1); if (y > mid) update(mid + 1, r, k << 1 | 1); sum[k] = sum[k << 1] + sum[k << 1 | 1]; } int query(int l, int r, int k) { if (x <= l && y >= r) return sum[k]; pushdown(l, r, k); int mid = l + r >> 1, res = 0; if (x <= mid) res = query(l, mid, k << 1); if (y > mid) res += query(mid + 1, r, k << 1 | 1); return res; } void init() { For(i, 0, 18) aa[i] = 1 << i; lg[1] = 0; For(i, 2, 300000) lg[i] = lg[i / 2] + 1; } void solve() { n = read(); build(1, n, 1); For(i, 1, n) pre[i] = 0; For(i, 1, n) { c[i] = read(); if (!pre[c[i]]) pre[c[i]] = i; suf[c[i]] = i; } For(i, 1, n) f[0][i] = suf[c[i]]; For(j, 1, 18) for (int i = 1; i + aa[j] - 1 <= n; i++) f[j][i] = max(f[j - 1][i], f[j - 1][i + aa[j - 1]]); long long ans = 0; For(i, 1, n) { int l = i; foR(j, 18, 0) if (l > aa[j]) { int x = l - aa[j]; if (qmax(x, i) <= i) l = x; } if (pre[c[i]] != i) { x = pre[c[i]] + 1; y = i; update(1, n, 1); } if (suf[c[i]] > i) continue; x = l; y = i; ans += query(1, n, 1); } cout << ans; } signed main() { init(); int _ = 1; _ = read(); while (_--) { solve(); cout << '\n'; } return 0; }