题目描述
对于 k = 1 , 2 , 3 , . . . , n k = 1,2,3,...,n k=1,2,3,...,n,分别求出最小的 m m m,使得存在一种将 n n n 个数划分成 m m m 段的方案,每段中不同数字的个数不超过 k k k 个。
1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105, 1 ≤ a i ≤ n 1 \leq a_i \leq n 1≤ai≤n。
输入样例:
5
1 3 4 3 3
输出样例:
4 2 1 1 1
分析:
首先很容易能够想到一个 n 2 n^2 n2 的暴力:那就是对于每一个询问 k k k,我们从起点往后拓展,如果当前段内的数字个数超过了 k k k,那么新开一段,否则就仍然使用当前段。最后输出段的数量就好了。可以证明这样的贪心是对的。
我们考虑怎样优化这一过程。现在的问题在于对于一个起点 x x x 而言,怎样快速找到一个长度 l e n len len,使得 [ x , x + l e n − 1 ] [x, x + len - 1] [x,x+len−1] 里面出现了 k k k 种数字。
注意到如果起点确定,那么影响 l e n len len 的长度的应该是 在起点右边,每种数字最靠左的位置。因为这些位置能够最先影响到区间内的数字种类数。
但是我们不能开一个二维数组去记录一每一个位置为起点,每种数字最靠左的位置。这样空间复杂度是不允许的并且也不好查询。 我们考虑 主席树。
我们维护一棵 可持久化线段树,对于每一个位置 i i i 而言,线段树的根为 r o o t i root_i rooti。具体来讲,我们倒序插入每个 a i a_i ai,如果 a i a_i ai 到当前为止是第一次出现的,我们就让 r o o t i root_i rooti 的 i i i 位置上加 1 1 1。否则,就让 r o o t i root_i rooti 的 l s t a i lst_{a_i} lstai 位置减 1 1 1,让 i i i 位置加 1 1 1。其中 l s t x lst_x lstx 表示 x x x 上一次出现的位置。
这样做的好处在于我们每次只用对线段树进行 单点修改,其它的直接继承就好。并且十分方便查询。
这样做,我们就可以在起点 x x x 的线段树上二分,找到第一个位置 y y y 使得 [ x , y ] [x, y] [x,y] 的种类数为 k k k。并且线段树中只需要维护 区间和 即可。
我们来计算一下时间复杂度:每一次二分的时间复杂度显然是 O ( l o g 2 n ) O(log_2n) O(log2n)。因为对于一个询问 k k k 而言,答案不会超过 n k \frac{n}{k} kn(每 k k k 个一段显然合法),那么所有询问的二分次数加起来是 ∑ i = 1 n n i ≈ n l o g 2 n \sum_{i = 1}^{n} \frac{n}{i} \approx nlog_2n ∑i=1nin≈nlog2n。所以总复杂度是 O ( n l o g 2 2 n ) O(nlog^2_2n) O(nlog22n)。
CODE:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
inline int read(){
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){if(c == '-') f = -1; c = getchar();}
while(isdigit(c)){x = (x << 1) + (x << 3) + (c ^ 48); c = getchar();}
return x * f;
}
int n, a[N], lst[N];
namespace Seg{
int tot, root[N];
struct SegmentTree{
int ls, rs, num;
#define ls(x) t[x].ls
#define rs(x) t[x].rs
#define num(x) t[x].num
}t[N * 25 * 2];
int build(){
tot++;
ls(tot) = rs(tot) = num(tot) = 0;
return tot;
}
void update(int p){num(p) = num(ls(p)) + num(rs(p));}
int ins(int p, int lp, int rp, int pos, int c){// 可持久化线段树
int tp = build(); t[tp] = t[p];
if(lp == rp){
num(tp) += c;
return tp;
}
int mid = (lp + rp >> 1);
if(pos <= mid) ls(tp) = ins(ls(tp), lp, mid, pos, c);
else rs(tp) = ins(rs(tp), mid + 1, rp, pos, c);
update(tp);
return tp;
}
int query(int p, int lp, int rp, int k){
if(lp == rp) return lp;
int mid = (lp + rp >> 1);
if(k <= num(ls(p))) return query(ls(p), lp, mid, k);
else return query(rs(p), mid + 1, rp, k - num(ls(p)));
}
}
int main(){
n = read();
for(int i = 1; i <= n; i++) a[i] = read();
for(int i = n; i >= 1; i--){
if(!lst[a[i]]){
Seg::root[i] = Seg::ins(Seg::root[i + 1], 1, n, i, 1);
lst[a[i]] = i;
}
else{
int k = Seg::ins(Seg::root[i + 1], 1, n, lst[a[i]], -1);
Seg::root[i] = Seg::ins(k, 1, n, i, 1);
lst[a[i]] = i;
}
}
for(int i = 1; i <= n; i++){
int now = 1, res = 0;
while(now <= n){
if(i >= Seg::num(Seg::root[now])) now = n + 1;
else now = Seg::query(Seg::root[now], 1, n, i + 1);//找到第 i + 1 个
res++;
}
printf("%d ", res);
}
return 0;
}