CF1654F

题面

定义长度为 \(2^n\) 字符串 \(s\) 是字符串 \(t\) 的"异或串" ,当且仅当存在一个数 \(j\) 使 \(\forall i\in[0,2^n-1],s_i=t_{i\oplus j}\) 。给定 \(t\) ,求 \(t\) 所有"异或串"中字典序最小的。

数据范围:\(n\le 18\)

题解

首先肯定最多只有 \(2^n\) 个"异或串",分别对应 \(j=0,1,..,2^n-1\)

那么我们就是要找到其中字典序最小的。

看到是异或,同时又涉及字典序的比较,就要意识到可以尝试倍增/分治。(只能说自己做的时候根本没有注意到...)

看到CF题解中的提示后想的分治,但是发现合并的时候没法判断,然后就G了。

下面是题解做法:

我们考虑倍增,从 \(1\sim n\) 枚举 \(i\) ,对于当前的 \(i\) ,我们考虑异或只对二进制最后 \(i\) 位有效的情况下,维护每个 \(j\) 对应的异或串的排名 \(rk_j\) 和排名为 \(j\) 的异或串对应的标号 \(id_j\)

你会发现这很像求后缀数组的过程,更进一步来说,这对应了一系列 倍增对字典序排序 的过程。

然后考虑 \(i\) 增加一位会带来什么影响。

参考后缀数组的过程,求 \(rj_{i+1,j}\) 的时候会参考 \((rk_{i,j},rk_{i,j+2^i})\) 这个二元组的大小,并以此作为权值排序。

套用在这里,求 \(rj_{i+1,j}\) 的时候会参考 \((rk_{i,j},rk_{i,j\oplus 2^{i+1}})\) ,那么之后就是正常的求后缀数组的过程了。

代码:

bool pd(int s1,int s2){
	if(rk[s1]^rk[s2])return rk[s1]<rk[s2];
	return rk[s1^op]<rk[s2^op];
}
for(int i=0;i<n;++i){
	op=1<<i;
	sort(id,id+m,pd);
	for(int j=1;j<m;++j)
		d[b[j]]=d[b[j-1]]+pd(id[j-1],id[j]);
	memcpy(rk,d,sizeof(int)*(m));
}

启发

  • 发现之前对后缀数组理解还是太浅显了,这类倍增给字典序排序的问题都可以这样解决。
posted @ 2022-03-23 21:33  qwq_123  阅读(44)  评论(0编辑  收藏  举报