CF786C Till I Collapse 整体二分+根号分治

题意:对于一个序列,假如说一个区间内最多能包含 $k$ 个不同的数,那么这个序列最少会被划分成几个区间 $?$

输出 $k$ 为 $1\sim n$ 的答案.

我们每次选区间一定是贪心地将这个区间选地越大越好.

这道题有一个非常显然的主席树做法:从后向前扫,维护每一种数字出现最靠左位置,然后用主席树维护这些关键位置.

假设当前跳到点 $k$,那么如果要查 $k$ 能跳到的下一个点的话在线段树上二分即可.

由于 $k$ 是由 $1\sim n$ ,所以整个暴力跳的复杂度大概是 $O(10\times 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; 
}

  

posted @ 2019-10-02 14:47  EM-LGH  阅读(269)  评论(0编辑  收藏  举报