题解:CF786C Till I Collapse

· · 题解

前言

实际上本题的根号分治可以不使用二分,以达到 O(n\sqrt{n}) 的复杂度。

思路

不难发现,分组的组数不会超过 $\lceil\frac{n}{k}\rceil$,考虑根号分治。 记 $B=\lceil{\sqrt{n}}\rceil$。 我们对于 $k\le B$ 的 $k$,直接暴力计算答案,时间复杂度 $O(n\sqrt{n})$; 对于 $k>B$ 的 $k$,由于其分组组数不超过 $B$,我们**记录每一组的右端点**,更新时只需要**将这 $B$ 个右端点尽量向右移,直到无法移动**即可。 右端点最多只会移动 $O(n\sqrt{n})$ 次,遍历的复杂度也是 $O(n\sqrt{n})$ 的,总时间复杂度为 $O(n\sqrt{n})$。 ### 实现细节 实现上,我们开 $B$ 个桶,第 $i$ 个桶记录第 $i$ 块的信息。当我们移动第 $i$ 块的右端点时,我们去更新第 $i$ 块和第 $i+1$ 块的桶(移动第 $i$ 块的右端点的同时也移动了第 $i+1$ 块的左端点),同时判断能否继续向右移。 当遇到一个块的右端点移到了 $n$ 时,说明答案就是这个块的编号,直接输出即可。 ### Code ```cpp #include<bits/stdc++.h> using namespace std; constexpr int maxn=1e5+10,maxs=330; int n,a[maxn],B,cnt[maxn],z[maxn],cz[maxs][maxn],ans[maxs]; signed main(){ ios::sync_with_stdio(0); cin.tie(0); cin>>n; for(int i=1;i<=n;i++)cin>>a[i]; B=ceil(sqrtl(n)); int tot=0; //这个循环是暴力,同时用 z 记录分界点 for(int i=1;i<=B;i++){ int last=1,res=0; tot=0; for(int j=1;j<=n;j++){ if(cnt[a[j]]==0 and res+1>i){ z[++tot]=j-1; for(int k=last;k<j;k++)cnt[a[k]]--; last=j; res=0; } if(cnt[a[j]]==0)res++; cnt[a[j]]++; } z[++tot]=n; for(int k=last;k<=n;k++)cnt[a[k]]--; cout<<tot<<" "; } //预处理 ans[i](第 i 块中数字的种类数)和 cz[i](对第 i 块开的桶) for(int i=1;i<=tot;i++){ for(int j=z[i-1]+1;j<=z[i];j++){ if(cz[i][a[j]]==0)ans[i]++; cz[i][a[j]]++; } } for(int i=B+1;i<=n;i++){ int eans=0; for(int j=1;j<=tot;j++){ //这里是有可能出现 z[j]<z[j-1] 的情况的,但是不会影响到答案统计。 //因为在 z[j]<z[j-1] 时,ans[j] 永远为 0,cz[j] 这个桶随着 z[j] 变大也会自动还原。 while(z[j]<n and ans[j]+(cz[j][a[z[j]+1]]==0)<=i){ z[j]++; ans[j]+=(cz[j][a[z[j]]]==0); cz[j][a[z[j]]]++; cz[j+1][a[z[j]]]--; ans[j+1]-=(cz[j+1][a[z[j]]]==0); //这里要计算对下一块的影响 } if(z[j]==n){eans=j;break;} } cout<<eans<<" "; } return 0; } ```