Codeforces Round #406 (Div. 1) C. Till I Collapse 主席树

链接:

http://codeforces.com/contest/786/problem/C

题意:

给你n个数,问最少能把这n个数分成连续的几段,且每段中不同的个数小于等于k个,输出k从1到n的答案。

题解:

我们知道i~(i,,,n)的不同数的个数肯定是递增的,所以对于每个i,我们可以通过二分得出一个最大的j使[i,j]中不同的数个数<=k。那么问题的关键在于,如何知道[i,j]这样一个区间中,不同的数的个数。

我们可以利用主席树,以root[i]为顶点的线段树存的是,[i,n]中不同的数个数,对于每颗树,将每个第一次出现的数的位置 置为1,其他重复出现的位置 置为0。

那么,对于每个k,刚开始我们位于root[1],二分找出值为k+1的位置r,ans[k]++,并将当前位置置成root[r],这样,我们面对的就是[r,n]这段数,直到r>n停止。

ed[i]表示最先出现i的位置,nex[i]表示在i位置的数下一次出现在哪个位置。

代码:

31 struct Node { int l, r, sum; };
32 int n, q;
33 int a[MAXN], ed[MAXN], nex[MAXN];
34 Node T[MAXN * 100];
35 int rt[MAXN], cnt;
36 int ans[MAXN];
37 
38 void update(int l, int r, int &x, int y, int pos, int val) {
39     T[++cnt] = T[y], T[cnt].sum += val, x = cnt;
40     if (l == r) return;
41     int m = (l + r) >> 1;
42     if (pos <= m) update(l, m, T[x].l, T[y].l, pos, val);
43     else update(m + 1, r, T[x].r, T[y].r, pos, val);
44 }
45 
46 int query(int l, int r, int x, int pos) {
47     if (l == r) return l;
48     int m = (l + r) >> 1;
49     if (T[T[x].l].sum >= pos) return query(l, m, T[x].l, pos);
50     else return query(m + 1, r, T[x].r, pos - T[T[x].l].sum);
51 }
52 
53 int main() {
54     ios::sync_with_stdio(false), cin.tie(0);
55     cin >> n;
56     rep(i, 1, n + 1) cin >> a[i], ed[i] = n + 1;
57     per(i, 1, n + 1) nex[i] = ed[a[i]], ed[a[i]] = i;
58     rep(i, 1, n + 1) update(1, n + 1, rt[1], rt[1], ed[i], 1);
59     rep(i, 2, n + 1) {
60         update(1, n + 1, rt[i], rt[i - 1], i - 1, -1);
61         update(1, n + 1, rt[i], rt[i], nex[i - 1], 1);
62     }
63     rep(i, 1, n + 1) {
64         int now = 1;
65         while (now <= n) {
66             now = query(1, n + 1, rt[now], i + 1);
67             ans[i]++;
68         }
69     }
70     rep(i, 1, n + 1) cout << ans[i] << ' ';
71     cout << endl;
72     return 0;
73 }

 

posted @ 2017-09-19 22:09  Flowersea  阅读(178)  评论(0编辑  收藏  举报