题意:对于一个序列,假如说一个区间内最多能包含 $k$ 个不同的数,那么这个序列最少会被划分成几个区间 $?$
输出 $k$ 为 $1sim n$ 的答案.
我们每次选区间一定是贪心地将这个区间选地越大越好.
这道题有一个非常显然的主席树做法:从后向前扫,维护每一种数字出现最靠左位置,然后用主席树维护这些关键位置.
假设当前跳到点 $k$,那么如果要查 $k$ 能跳到的下一个点的话在线段树上二分即可.
由于 $k$ 是由 $1sim n$ ,所以整个暴力跳的复杂度大概是 $O(10 imes nlogn)$ 左右的.
还有一个整体二分的做法:
当 $k<=sqrt n$ 时,可以直接暴力跳 $sqrt n$ 次.
对于 $k>sqrt n$ 时,会发现答案会随 $k$ 的增大而减小,而答案最多也只有 $sqrt n$ 种.
可以考虑整体二分:
使用 $solve(l,r)$ 直到左端点和右端点答案相等位置.
#include <bits/stdc++.h> #define N 100003 #define ll long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; int A[N],C[N],ans[N],n; int solve(int x) { int cur=0,ans=0,ls=0,i,j,rt=0; memset(C,0,sizeof(C)); for(i=1;i<=n;++i) { ++C[A[i]]; if(C[A[i]]==1) { ++rt; } if(rt>x) { --i; for(j=ls;j<=i+1;++j) C[A[j]]--; rt=0,ls=i+1,++ans; } } ++ans; return ans; } void div_con(int l,int r) { int L=solve(l), R=solve(r); if(L==R) { for(int j=l;j<=r;++j) ans[j]=L; } else { int mid=(l+r)>>1; div_con(l,mid), div_con(mid+1,r); } } int main() { int i,j; // setIO("input"); scanf("%d",&n); for(i=1;i<=n;++i) scanf("%d",&A[i]); for(i=1;i*i<=n;++i) ans[i]=solve(i); div_con(i, n); for(i=1;i<=n;++i) printf("%d ",ans[i]); return 0; }