CF1246F Cursor Distance

闲谈:gcz 回 CJ 讲课,对着 PPT 上这道 3500 的题讲:我们来讲一道“例题”,然后指着 PPT 上 3100 的题讲:我们来讲一道“水题”

结果对着 PPT 和题解理解了好久才弄明白这一道“例题”……

闲扯结束

首先发现这道题如果从起点开始跳距离似乎没有什么规律,正难则反,考虑能在 \(k\) 步以内跳到 \(i\) 的区间 \([L_{i,k},R_{i,k}]\)

那么答案就是:

\[\begin{align} \sum_{i=1}^{n}\sum_{j=1}^{n}\text{dist}(i,j)&=\sum_{i=1}^{n}\sum_{j=1}^{n}\sum_{k=0}^{n} [i \notin [L_{j,k},R_{j,k}]]\\ &=\sum_{j=1}^{n} \sum_{k=0}^{n} n-R_{j,k}+L_{j,k}-1\\ &=(n-1)n(n+1)-\sum_{i=1}^{n}\sum_{k=0}^{n} R_{i,k} + \sum_{i=1}^{n}\sum_{k=0}^{n} L_{i,k}\\ &=(n-1)n(n+1)-\sum_{i=1}^{n}\sum_{k=1}^{n} R_{i,k} + \sum_{i=1}^{n}\sum_{k=1}^{n} L_{i,k} \end{align} \]

(注意到 \(L_{i,0}=R_{i,0}=i\)

考虑求 \(L_{i,k},R_{i,k}\) ,不难发现这是一个不断向两边扩大的区间,它拥有转移:

\[[L_{i,k},R_{i,k}]=\cup_{j\in [L_{i,k-1},R_{i,k-1}]} [L_{j,1},R_{j,1}] \]

意义是能在 \(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\) 数组,具体参照官方题解写法吧

posted @ 2022-03-03 22:13  yyyyxh  阅读(63)  评论(0编辑  收藏  举报