1. 程式人生 > 實用技巧 >CF786C Till I Collapse(根號分治)題解

CF786C Till I Collapse(根號分治)題解

題意

\(n\)個數劃分成\(m\)段使得每中不同數字的個數\(\le k\),對於每個\(k\)滿足\(1\le k\le 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("\n");
    return 0;
}