Processing math: 100%

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;
}

 

posted @   uid001  阅读(298)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示