CF786C Till I Collapse(根號分治)題解
阿新 • • 發佈:2020-11-29
題意
將\(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; }