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\)。
那么多出来的部分就是:
这里便是利用了倍增的性质:每次多出来的编号只有最高位与原来不同且原来的最高位为 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;
}