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 }