大意: 给定序列$a$, 元素范围$[1,C]$, 求一个最长子序列, 满足每个元素要么不出现, 要么出现次数$\le K$.
枚举右端点, 考虑左端点合法的位置. 显然一定是$C$种颜色合法位置的并, 可以用线段树维护合法颜色的种类数, 每次二分出最小的满足合法个数为$C$的位置更新答案.
考虑右端点移动到$i$, 存在一个位置$p$, 满足
对于颜色$a_i$的合法区间为$[1,p]$, 不合法区间为$[p+1,i]$.
可以求出上一次计算的$a_i$合法位置的增量与不合法位置的增量, 用线段树区间加减即可.
类似题目可以做一下[POI2015]KIN, 也是对每种颜色维护一个增量.
#include <iostream> #include <cstdio> #include <queue> #define REP(i,a,n) for(int i=a;i<=n;++i) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define hr puts("") using namespace std; const int N = 1e6+10; int n, c, k, a[N]; vector<int> v[N]; struct _ { int ma,tag,pos; void upd(int x) {ma+=x,tag+=x;} _ operator + (const _ &rhs) const { _ ret; ret.ma = max(ma, rhs.ma); ret.pos = ret.ma==ma?pos:rhs.pos; ret.tag = 0; return ret; } } tr[N<<2]; void build(int o, int l, int r) { tr[o].ma=c,tr[o].tag=0,tr[o].pos=l; if (l!=r) build(ls),build(rs); } void pd(int o) { if (tr[o].tag) { tr[lc].upd(tr[o].tag); tr[rc].upd(tr[o].tag); tr[o].tag=0; } } void add(int o, int l, int r, int ql, int qr, int v) { if (l>qr||r<ql) return; if (ql<=l&&r<=qr) return tr[o].upd(v); pd(o),add(ls,ql,qr,v),add(rs,ql,qr,v),tr[o]=tr[lc]+tr[rc]; } int qry(int o, int l, int r, int ql, int qr) { if (l>qr||r<ql||tr[o].ma!=c) return 0; if (ql<=l&&r<=qr) return tr[o].pos; pd(o); int t = qry(ls,ql,qr); return t?t:qry(rs,ql,qr); } int main() { while (~scanf("%d%d%d",&n,&c,&k)) { REP(i,1,n) scanf("%d",a+i); REP(i,1,c) v[i].clear(),v[i].pb(0); int ans = 0; build(1,1,n); REP(i,1,n) { if (v[a[i]].back()+1<=i) add(1,1,n,v[a[i]].back()+1,i,-1); v[a[i]].pb(i); int p = v[a[i]].size()-k-1; if (p>=0) add(1,1,n,v[a[i]][p]+1,v[a[i]][p+1],1); int j = qry(1,1,n,1,i); if (j) ans = max(ans, i-j+1); } printf("%d\n",ans); } }
原文地址:https://www.cnblogs.com/uid001/p/11242982.html
时间: 2024-10-23 06:16:04