Till I Collapse CodeForces - 786C (主席树区间加,二分最小值)
大意: 给定序列, 将序列划分为若干段, 使得每段不同数字不超过k, 分别求出k=1...n时的答案.
考虑贪心, 对于某个k
从1开始, 每次查询最后一个颜色数<=k的点作为一个划分, 直到全部划分完毕
由于每个划分大小至少为k, 故最多需要查询nk次, 所以总共需要查询O(nlogn)次.
查询操作考虑用主席树实现.
对序列中每个点维护一棵线段树, 对于位置x的线段树, [x,n]的每个位置存它到点x的种类数, 非叶结点存儿子的最小值用来二分.
从大到小更新, 这样就相当于每次对[x,nxt[a[x]]-1]位置进行区间加, 可以用标记永久化来优化.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | #include <iostream> #include <cstdio> #define REP(i,a,n) for(int i=a;i<=n;++i) #define PER(i,a,n) for(int i=n;i>=a;--i) #define hr putchar(10) #define lc tr[o].l #define rc tr[o].r #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r using namespace std; const int N = 1e5+10; int n, tot, a[N], nxt[N], T[N]; struct { int l,r,v;} tr[N<<6]; void add( int &o, int l, int r, int ql, int qr) { tr[++tot] = tr[o], o = tot; if (ql<=l&&r<=qr) return ++tr[o].v, void (); int tag = tr[o].v-min(tr[lc].v,tr[rc].v); if (mid>=ql) add(ls,ql,qr); if (mid<qr) add(rs,ql,qr); tr[o].v = tag+min(tr[lc].v,tr[rc].v); } int find( int o, int l, int r, int x, int k) { if (l==r) return l; k -= tr[o].v-min(tr[lc].v,tr[rc].v); if (mid<x) return find(rs,x,k); return tr[rc].v>k?find(ls,x,k):find(rs,x,k); } int solve( int k) { int ans = 0, now = 1; while (now<=n) { now = find(T[now],1,n,now,k)+1; ++ans; } return ans; } int main() { scanf ( "%d" , &n); REP(i,1,n) scanf ( "%d" , a+i); PER(i,1,n) { T[i] = T[i+1]; add(T[i],1,n,i,nxt[a[i]]?nxt[a[i]]-1:n); nxt[a[i]] = i; } REP(i,1,n) printf ( "%d " , solve(i));hr; } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步