CF786C Till I Collapse(根号分治)题解

题意

(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;
}
原文地址:https://www.cnblogs.com/whenc/p/14057744.html