题意:给你一个串,问满足以下条件的子串中最长的是多长:对于每个数字,要么在这个子串没出现过,要么出现次数超过k次。
思路:我们考虑枚举右端点,对于当前右端点,我们单独考虑每一种数的合法区间。假设当前枚举的右端点是i,考虑的数字是c,在右端点左边离i最近的数字c的位置是p1,离i第k远的数字c的位置是p2, 容易发现,数字c的合法区间为[1, p2]和[p1 + 1, i],对应的情况是选择这个数至少k个和不选这个数。用线段树维护区间,对每一个数的合法区间+1,找到第i个点前面值为c的最小的位置就是右端点为i的最优解。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 10;
int n, c, k, a[N], tree[N << 2], tag[N << 2], now[N];
vector<int> v[N];
void pushup(int rt) {
tree[rt] = max(tree[rt << 1 | 1], tree[rt << 1]);
}
void pushdown(int rt) {
if(tag[rt]) {
tag[rt << 1] += tag[rt];
tag[rt << 1 | 1] += tag[rt];
tree[rt << 1] += tag[rt];
tree[rt << 1 | 1] += tag[rt];
tag[rt] = 0;
}
}
void update(int l, int r, int L, int R, int rt, int v) {
if(L > R)
return;
if(L <= l && r <= R) {
tree[rt] += v;
tag[rt] += v;
return;
}
int m = (l + r) >> 1;
pushdown(rt);
if(L <= m)
update(l, m, L, R, rt << 1, v);
if(m < R)
update(m + 1, r, L, R, rt << 1 | 1, v);
pushup(rt);
}
int query(int rt, int l, int r) {
if(l == r)
return l;
int ans = -1, m = (l + r) >> 1;
pushdown(rt);
if(tree[rt << 1] == c)
ans = query(rt << 1, l, m);
else if(tree[rt << 1 | 1] == c)
ans = query(rt << 1 | 1, m + 1, r);
return ans;
}
int main() {
while(~scanf("%d%d%d", &n, &c, &k)) {
for(int i = 0; i <= c; i++)
v[i].clear(), v[i].push_back(0);
for(int x, i = 1; i <= n; i++)
scanf("%d", &a[i]), v[a[i]].push_back(i);
memset(tree, 0, sizeof tree);
memset(tag, 0, sizeof tag);
for(int i = 1; i <= c; i++) {
v[i].push_back(n + 1);
update(1, n, v[i][0] + 1, v[i][1] - 1, 1, 1);
now[i] = 0;
}
int ans = 0;
for(int i = 1; i <= n; i++) {
int t = a[i];
update(1, n, v[t][now[t]] + 1, v[t][now[t] + 1] - 1, 1, -1);
if(now[t] >= k)
update(1, n, 1, v[t][now[t] - k + 1], 1, -1);
now[t]++;
update(1, n, v[t][now[t]] + 1, v[t][now[t] + 1] - 1, 1, 1);
if(now[t] >= k)
update(1, n, 1, v[t][now[t] - k + 1], 1, 1);
int tmp = query(1, 1, n);
if(tmp != -1)
ans = max(ans, i - tmp + 1);
}
printf("%d\n", ans);
}
return 0;
}