题意
将(n)个数划分成(m)段使得每中不同数字的个数(le k),对于每个(k)满足(1le kle n)求出最小的(m)。
算法
根号分治+二分
思路
面对那不大不小的数据范围,难以优化的查询方法以及令人疑惑的值域,我们可以联想到根号分治。
首先明确:
- 答案的范围是不会超过(frac{n}{k})的。(就算全都不一样也只要这么多次划分)
- 当(k)单调递增时,答案单调不减,即答案具有二分性质
根据第一条性质,我们发现,不同答案的个数是不超过(sqrt{n})级别的,而结合第二条性质,答案相同的(k)一定是连续的。
所以我们得出一下做法:
-
对于(k le sqrt{n})的情况:直接暴击(O(n))统计;
-
对于(k > sqrt{n})的情况:也是暴力统计,但统计完以后,需要二分出与当前答案相同的区间,将这段区间的答案一起输出。
参考代码
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn = 1e5 + 10;
int n,a[maxn],Ans,siz;
bool T[maxn];
int work(int k){
memset(T,0,sizeof(T));
int sum = 0, cnt = 0, sta = 1;
for(int i = 1; i <= n; ++ i){
if(!T[a[i]]) T[a[i]] = 1, cnt ++;
if(cnt > k){
sum++, cnt = 1;
for(int j = sta; j < i; ++ j) T[a[j]] = 0;
T[a[i]] = 1; sta = i;
}
}
if(cnt) sum++;
return sum;
}
int main(){
scanf("%d", &n); siz = sqrt(n);
for(int i = 1; i <= n; ++ i) scanf("%d", a + i);
for(int k = 1; k <= n; ++ k){
if(k <= siz) printf("%d ", work(k));
else{
Ans = work(k);
int l = k, r = n, mid;
while(l <= r){
mid = (l + r) >> 1;
if(work(mid) < Ans) r = mid - 1;
else l = mid + 1;
}
for(int i = k; i <= l - 1; ++ i) printf("%d ", Ans);
k = l - 1;
}
}
printf("
");
return 0;
}