CF778 Div1+Div2 F Minimal String Xoration

CF778 Div1+Div2 F Minimal String Xoration

链接:https://codeforces.ml/contest/1654/problem/F


我们设 \(f(k)\) 表示一个字符串 \(t\) 满足 \(\forall i\;t_i=s_{i\oplus k}\)。那么也就是对所有的 \(f(k)\; k\in[0,2^n-1]\) 进行排序,然后字典序最小的那个就是答案。

注意到由于长度是 \(2\) 的正次幂,我们如果每次将长度除以 \(2\)。相当于是把最高位为 \(1\) 的编号舍弃。观察舍弃掉的编号和留下来的编号在二进制下的表示。发现除了最高位他们都是相等的。而这对于处理异或来说比较方便。这启示我们是不是可以使用倍增来解决问题。

我们发现我们要做的工作与 SA 类似,也是倍增,也是排序,这启示我们可以用类似于求 SA 的方式来求解这个问题。

\(f(k,w)\) 表示长度为 \(2^w\) 的字符串 \(t\) 满足 \(\forall i\; t_i=s_{i\oplus k}\)。假设我们已经求得了一个关于 \([0,2^n-1]\) 的排列 \(A\)。并且 \(A\) 满足 \(f(A_i,w)<f(A_{i+1},w)\)。那么我们的任务就是根据这个数组求得一个新的排列 \(A'\) 满足 \(f(A'_i,w+1)<f(A'_{i+1},w+1)\)

考虑构造一个 \(rk\) 数组,使得 \(rk_i\) 表示 \(f(i,w)\) 的排名。由于我们已经知道了 \(A\),可以很轻易的得到这个 \(rk\)

接下来我们要求得一个新的排列 \(A'\)

对于 \(f(i,w)\) 来说,由于长度倍长了,多出来的部分即 \(2^w\)\(2^{w+1}-1\) 这这一段。

定义字符串的加法为拼接。即 \(a+b=ab\)

那么多出来的部分就是:

\[\begin{aligned}&\sum_{i=2^w}^{2^{w+1}-1}s_{i\oplus k}\\&=\sum_{i=0}^{2^w-1}s_{(i+2^w)\oplus k}\\&=\sum_{i=0}^{2^w-1}s_{(i\oplus2^w)\oplus k}\\&=\sum_{i=0}^{2^w-1}s_{i\oplus(2^w\oplus k)}\end{aligned} \]

这里便是利用了倍增的性质:每次多出来的编号只有最高位与原来不同且原来的最高位为 0,新的最高位位 1,所以能利用异或来代替加法。

由于 \(2{^w}\oplus k\) 一定是在 \([0,2^{n}-1]\) 的范围里面的,相当于是多出来这部分字符串与 \(f(i\oplus2^w,w)\) 是相同的。

所以所有新增出来的字符串都是原来已有的(即为 \(f(i,w)\) 中的某一个)。而他们的大小关系我们已经求好了。

而由于 \(f(i,w+1)=f(i,w)+f(i\oplus 2^w,w)\)。因此我们进行一个双关键字排序即可得到 \(A'\)

写法与 SA 相似。如果用 sort 排序复杂度就是 \(O(n^2\times 2^n)\),用基数排序是 \(O(n\times 2^n)\) 的。这里用 sort 的复杂度足够了。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = (1<<18)+5;
int n,rk[MAXN],A[MAXN],od[MAXN];
char S[MAXN];
int main()
{
	scanf("%d %s",&n,S);
	n=(1<<n);
	for(int i=0;i<n;++i) A[i]=i,rk[i]=S[i];
	for(int w=1;w<n;w<<=1)
	{
		sort(A,A+n,[&](int x,int y)
		{
			return rk[x]==rk[y]?rk[x^w]<rk[y^w]:rk[x]<rk[y];
		});
		for(int i=0;i<n;++i) od[i]=rk[i];
		for(int i=0,p=0;i<n;++i)
			rk[A[i]]=(i&&od[A[i]]==od[A[i-1]]&&od[A[i]^w]==od[A[i-1]^w])?p:++p;
	}
	for(int i=0;i<n;++i)
		printf("%c",S[A[0]^i]);
	return 0;
}
posted @ 2022-03-22 22:31  夜空之星  阅读(34)  评论(0编辑  收藏  举报