【CF786C】Till I Collapse
阿新 • • 發佈:2021-07-12
題目
題目連結:https://codeforces.com/problemset/problem/786/C
將 \(n\) 個數劃分成 \(m\) 段使得每中不同數字的個數 \(\le k\)。對於每個 \(k\) 滿足 \(1\le k\le n\) 分別求出最小的 \(m\)。
\(n\leq 10^5,1\leq a_i\leq n\)。
思路
顯然每次貪心選擇是最優的。由於總分段次數的上界是 \(\sum^{n}_{i=1}\frac{n}{i}=O(n\log n)\) 的,所以我們可以考慮每次找出分段的右邊界。
假設上一次分段的位置是 \(p\),我們預處理出 \(pre[i]\) 表示位置 \(i\)
採用主席樹就可以輕鬆解決。時間複雜度 \(O(n\log^2 n)\)。
程式碼
#include <bits/stdc++.h> using namespace std; const int N=100010,LG=18; int n,a[N],pre[N],last[N],rt[N]; vector<int> pos[N]; struct SegTree { int tot,lc[N*LG*4],rc[N*LG*4],cnt[N*LG*4]; int update(int now,int x,int l,int r,int k) { if (!x || x==now) x=++tot,lc[x]=lc[now],rc[x]=rc[now],cnt[x]=cnt[now]+1; else cnt[x]++; if (l==r) return x; int mid=(l+r)>>1; if (k<=mid) lc[x]=update(lc[now],lc[x],l,mid,k); else rc[x]=update(rc[now],rc[x],mid+1,r,k); return x; } int query(int x,int l,int r,int k) { if (l==r) return l; int mid=(l+r)>>1; if (cnt[lc[x]]>=k) return query(lc[x],l,mid,k); else return query(rc[x],mid+1,r,k-cnt[lc[x]]); } }seg; int main() { scanf("%d",&n); for (int i=1;i<=n;i++) { scanf("%d",&a[i]); pre[i]=last[a[i]]; last[a[i]]=i; pos[pre[i]].push_back(i); } pos[0].push_back(n+1); for (int i=0;i<=n;i++) { for (int j=0;j<pos[i].size();j++) rt[i]=seg.update(i?rt[i-1]:0,rt[i],1,n+1,pos[i][j]); if (!rt[i]) rt[i]=rt[i-1]; } for (int i=1;i<=n;i++) { int ans=0; for (int j=0;j<n;ans++) j=seg.query(rt[j],1,n+1,j+i+1)-1; cout<<ans<<" "; } return 0; }