CF1246F Cursor Distance
闲谈:gcz 回 CJ 讲课,对着 PPT 上这道 3500 的题讲:我们来讲一道“例题”,然后指着 PPT 上 3100 的题讲:我们来讲一道“水题”
结果对着 PPT 和题解理解了好久才弄明白这一道“例题”……
闲扯结束
首先发现这道题如果从起点开始跳距离似乎没有什么规律,正难则反,考虑能在 \(k\) 步以内跳到 \(i\) 的区间 \([L_{i,k},R_{i,k}]\)
那么答案就是:
(注意到 \(L_{i,0}=R_{i,0}=i\) )
考虑求 \(L_{i,k},R_{i,k}\) ,不难发现这是一个不断向两边扩大的区间,它拥有转移:
意义是能在 \(k-1\) 步内跳到 \(j\) 的节点再跳一步即可跳到 \(i\) ,其中 \(L_{i,1},R_{i,1}\) 都很好求
但这样即使用线段树优化扩展一个区间,每个点依然可能要扩展 \(O(n)\) 次,无法接受
我们现在肯定需要考虑一个能一次性扩展多次的做法,这种时候可以考虑倍增
但是由于左右端点都在扩展当中,每一次需要并起来的区间会变多,不好倍增
这道题的精华就在这里,发现对于一个区间,比如 \(\texttt{abcdabc}\) 来说,此时如果要扩展 \(L_{i,k}\) ,那么后面三个字母是没意义的,这是因为他们的 \(L_{j,1}\) 不会比他们字母相同的最靠左边的位置的 \(L_{j,1}\) 更小
那么对于一个固定的字符集大小 \(|\Sigma|\) 和一个固定的区间左边界 \(L_{i,k}\) 来说,它转移到的 \(L_{i,k+1}\) 是固定的
那么我们就可以枚举字符集大小 \(|\Sigma|\) ,然后对于每一个边界 \(L,R\) 分开倍增,倍增的过程中维护和,倍增的边界是当前区间的字符集大小大于 \(|\Sigma|\) 或 \(L,R\) 倍增的次数大于 \(n\) ,这两个东西用 \(nxt\) 和 \(cnt\) 数组判掉即可
#include <cstdio>
#include <algorithm>
#pragma GCC optimize(2,3,"Ofast")
using namespace std;
const int _=200003;
char s[_];
int pre[_][27],nxt[_][27];
int tmp[26],a[_],n;
int L[_],R[_],cl[_],cr[_];
int fl[18][_],fr[18][_];
int cnt[_];
long long sl[18][_],sr[18][_];
int main(){
scanf("%s",s);
while(s[n]>='a'&&s[n]<='z') a[n+1]=s[n]-'a',++n;
for(int i=0;i<26;++i) tmp[i]=0;
for(int i=1;i<=n;++i){
L[i]=tmp[a[i]];
L[i]=max(L[i],1);
tmp[a[i]]=i;
for(int j=0;j<26;++j)
pre[i][j]=tmp[j];
pre[i][26]=0;
sort(pre[i],pre[i]+26,greater<int>());
}
for(int i=0;i<26;++i) tmp[i]=n+1;
for(int i=n;i;--i){
R[i]=tmp[a[i]];
R[i]=min(R[i],n);
tmp[a[i]]=i;
for(int j=0;j<26;++j)
nxt[i][j]=tmp[j];
nxt[i][26]=n+1;
sort(nxt[i],nxt[i]+26,less<int>());
}
long long res=1ll*n*n*n-n;
for(int i=1;i<=n;++i){
cl[i]=cr[i]=i;
sl[0][i]=fl[0][i]=L[i];
sr[0][i]=fr[0][i]=R[i];
cnt[i]=n;
}
L[0]=L[n+1]=n+1;R[0]=R[n+1]=0;
for(int t=1;t<=26;++t){
for(int o=1;o<18;++o){
for(int i=1;i<=n;++i){
fl[o][i]=fl[o-1][fl[o-1][i]];
fr[o][i]=fr[o-1][fr[o-1][i]];
sl[o][i]=sl[o-1][fl[o-1][i]]+sl[o-1][i];
sr[o][i]=sr[o-1][fr[o-1][i]]+sr[o-1][i];
}
}
for(int o=17;~o;--o){
for(int i=1;i<=n;++i){
int l=fl[o][cl[i]],r=fr[o][cr[i]];
if(nxt[l][t]>r&&cnt[i]>(1<<o)){
res+=sl[o][cl[i]]-sr[o][cr[i]];
cl[i]=l;cr[i]=r;
cnt[i]-=(1<<o);
}
}
}
for(int i=1;i<=n;++i){
if(nxt[cl[i]][t]>cr[i]&&cnt[i]){
cl[i]=fl[0][cl[i]];
cr[i]=fr[0][cr[i]];
res+=cl[i]-cr[i];
--cnt[i];
}
}
for(int i=1;i<=n;++i){
sl[0][i]=fl[0][i]=min(fl[0][i],L[nxt[i][t]]);
sr[0][i]=fr[0][i]=max(fr[0][i],R[pre[i][t]]);
}
}
printf("%lld\n",res);
return 0;
}
之前代码被卡常了,交换了一下倍增数组的两维才 A ,后来发现其实可以不需要 \(cnt\) 数组,具体参照官方题解写法吧