CF2092F Andryusha and CCB 题解
CF 官解感觉跳了很多步啊,自己写一篇造福后人。
首先肯定是不能直接求的,考虑转化贡献体。对划分的段数转化贡献依旧不好求,考虑对每个子串的美感度转化贡献,即先枚举每个子串的美感度。
然后发现这样枚举是非常优异的。首先,划分子串不能增加总的美感度,而划分 \(c\) 段至多减少 \(c-1\) 的美感度。于是随着美感度 \(k\) 在枚举中增加,可划分的子串数量在减少。假设总的美感度为 \(x\),对于 \(k\) 能划分的子串数量是 \(O(\frac{x}{k})\) 级别的。根据调和级数知识,我们枚举划分的段数时间复杂度是 \(O(n\log n)\) 的。
其次,这样一定不会算重,即对于每个前缀,每个划分的段数只会在一个 \(k\) 里被计算。假设对于某个前缀某个划分的段数 \(c\) 在两个不同的 \(k_1,k_2\) 中被计算,不妨设 \(k_1\lt k_2\)。则至少需要划分 \(c(k_2-k_1)+1\) 段才能减少足够的美感度以满足总美感度相同,因为 \(k_2-k_1\gt 0\),所以 \(c(k_2-k_1)+1\gt c\),矛盾,故不会算重。
然后我们考虑每一对 \((k,c)\) 的贡献。现在我们只关心 \(0\) 和 \(1\) 交界的地方,因为只有这里能产生美感度。因此,我们把序列改写成极长的 \(0\) 或 \(1\) 连续段,每个元素代表一个连续段。这样写的好处是相邻元素一定会贡献 \(1\) 点美感度。以下下标均指改写后的序列,从 \(1\) 开始。
直接求也是不好做的,于是我们考虑递推。如果 \(c=1\),显然只有 \(k+1\) 结尾的前缀满足要求。如果 \(c=2\),直接把后面 \(k+1\) 个元素作为下一段就行,是 \(2k+2\) 结尾的前缀。特别的,如果 \(k+1\) 号元素长度大于 \(1\),我们可以考虑然后后面的前缀延伸进 \(k+1\) 号元素,这样 \(2k+1\) 结尾的前缀也满足条件。
这启发我们考虑贡献的连续性。具体的,如果 \(c-1\) 的贡献区间是 \([l,r]\),考虑递推 \(c\) 的贡献区间。同上所述,\([l+k+1,r+k+1]\) 一定是可以的。想要延长这一区间,我们只需要考虑 \(l+k+1\) 这一边界,因为其他前缀减 \(1\) 后已经被包含。如果第 \(l\) 号元素长度大于 \(1\),可以让第 \(c\) 个子串延伸进第 \(l\) 号元素,所以 \(l+k\) 结尾的前缀可以取到。
最后,我们记录每一个极长连续段的左边界和右边界,差分做区间加即可。
注意 \(k=0\) 的时候需要特别计算。
#include <bits/stdc++.h>
using namespace std;
long long t,n,a[2000000],b[2000000],l[2000000],r[2000000],ans[2000000];
int main()
{
scanf("%lld",&t);
while(t--)
{
scanf("%lld",&n);
long long m=0;
for(int i=1;i<=n;i++)
{
scanf("%1lld",&a[i]);
if(i==1||a[i]!=a[i-1])b[++m]=1,l[m]=r[m]=r[m-1]+1;
else b[m]++,r[m]++;
}
for(int i=0;i<=n+1;i++)ans[i]=0;
for(int i=1;i<m;i++)
{
long long pl=i+1,pr=i+1;
ans[l[pl]]++,ans[r[pr]+1]--;
for(int j=2;j<=m/i;j++)
{
if(b[pl]==1)pl=pl+i+1,pr=pr+i+1;
else pl=pl+i,pr=pr+i+1;
if(pr>m&&pl<=m)ans[l[pl]]++,ans[r[m]+1]--;
else if(pr<=m)ans[l[pl]]++,ans[r[pr]+1]--;
}
}
for(int i=1;i<=n;i++)ans[i]+=ans[i-1];
for(int i=1;i<=m;i++)
for(int j=l[i];j<=r[i];j++)
ans[j]+=(j-i+1);
for(int i=1;i<=n;i++)printf("%lld ",ans[i]);
printf("\n");
}
return 0;
}

浙公网安备 33010602011771号