F. Minimal String Xoration(后缀数组(radix sort)2800 GOOD)

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

后缀数组

  • 基于倍增和基数排序的思路求出字符串每个后缀的字典序列rank

性质

  • 思路:对所有可能的字串进行排序,并不需要一些DP或贪心!
  • 对于本道题可以多写一些字符串以发现一些规律
  • 设原始字符串为 s 0 s 1 ⋯ s n s_0s_1 \cdots s_n s0s1sn
  • 关键步骤:

设选用 s i s_i si作为逐个异或的字符串,则最终字符串变为
s 0 ⊕ i , s 1 ⊕ i , ⋯ s n ⊕ i s_{0 \oplus i},s_{1 \oplus i},\cdots s_{n \oplus i} s0i,s1i,sni
考察长度为 2 k 2^k 2k的字符串前缀
s 0 ⊕ i , ⋯ s 2 k − 1 − 1 ⊕ i s 2 k − 1 ⊕ i ⋯ s 2 k − 1 ⊕ i s_{0 \oplus i},\cdots s_{2^{k-1} -1\oplus i} s_{2^{k-1} \oplus i} \cdots s_{2^{k}-1 \oplus i} s0i,s2k11is2k1is2k1i
后半部分 s 2 k − 1 ⊕ i ⋯ s 2 k − 1 ⊕ i s_{2^{k-1} \oplus i} \cdots s_{2^{k}-1 \oplus i} s2k1is2k1i
相当于原始字符串与 s 2 k − 1 ⊕ i s_{2^{k-1} \oplus i} s2k1i异或后的长度为 2 k − 1 2^{k-1} 2k1前缀。

  • 最后,利用后缀数组(实质上是基数排序)倍增的思路,维护所有可能的字符串2的幂次的前缀即可。
#include<iostream>
#include<vector>
#include<string>
#include<set>
#include<algorithm>
#include<map>
#include<queue>
#include <chrono>
#include<math.h>
#include<unordered_map>
#include <bits/stdc++.h>
using namespace std;
using namespace std;
const int N = 1e5+5;
const int S =  500;
const long long mod = 1e9+7;
typedef long long ll;


int main()
{
    int n;cin>>n;
    string s;
    cin >> s;
    int v[1<<n];
    int a[1<<n];
    for(int i = 0;i<1<<n;i++) a[i]=i;
    sort(a,a+(1<<n),[&](int i,int j){return s[0^i]<s[0^j];});
    //for(int i = 0;i<1<<n;i++) cout << a[i] << endl;
    //该排名需要注意字符相等的情况
    int pre = 1;
    for(int i = 0;i<1<<n;i++)
    {
        if(i==0) v[a[i]]=pre;
        else 
        {
            if(s[a[i]]==s[a[i-1]]) v[a[i]]=pre;
            else v[a[i]]=++pre;
        }
    }
    //for(int i = 0;i<1<<n;i++) cout << v[i] << endl;
    for(int i = 1;i<=n;i++)   //将幂次从1增加到n
    {
        //for(int j = 0;j<1<<n;j++) a[j]=j;
        sort(a,a+(1<<n),[&](int p,int q){return make_pair(v[p],v[p ^ (1 << (i-1))]) < make_pair(v[q],v[q ^ (1 << (i-1))]);});    
        pre = 1;
        int v_pre[1<<n]; memcpy(v_pre,v,(1<<n)*sizeof(int));
        for(int j = 0;j<1<<n;j++)
        {
            if(j == 0) v[a[j]]=j+1;
            else
            { 
                if(make_pair(v_pre[a[j]],v_pre[a[j] ^ (1 << (i-1))]) == make_pair(v_pre[a[j-1]],v_pre[a[j-1] ^ (1 << (i-1))]))
                v[a[j]]=pre;
                else v[a[j]]=++pre;
            }
        }
    }
    int index = a[0];
    for(int i = 0;i<s.size();i++) cout<<s[i^index];
    system("pause");
    return 0;
}

标准答案代码
#include <bits/stdc++.h>
using namespace std;
 
const int N = 18;
string s;
int a[1<<N], v[1<<N], tmp[1<<N];
 
int main() {
    ios_base::sync_with_stdio(0); cin.tie(0);
    int n; cin >> n >> s;
    assert(s.size() == (1<<n));
 
    iota(a, a+(1<<n), 0);
    sort(a, a+(1<<n), [&](int i, int j){ return s[i] < s[j]; });
    for (int i = 1; i < 1<<n; i++)
        v[a[i]] = v[a[i-1]] + (s[a[i]] != s[a[i-1]] ? 1 : 0);
 
    for (int k = 1; k < 1<<n; k <<= 1) {
        auto cmp = [&](int i, int j){
            return v[i] == v[j] ? v[i^k] < v[j^k] : v[i] < v[j];
        };
        sort(a, a+(1<<n), cmp);
        for (int i = 1; i < 1<<n; i++)
            tmp[a[i]] = tmp[a[i-1]] + (cmp(a[i-1], a[i]) ? 1 : 0);
        copy(tmp, tmp+(1<<n), v);
    }
 
    for (int i = 0; i < 1<<n; i++)
        cout << s[i^a[0]];
    cout << "\n";
}

代码分析

  • 注意cmp的写法
  • 关于每次循环中,a是否需要重新初始化?
    • 似乎需要
    • 但做与不做均能AC
    • 使用数学归纳法可以证明。事实上,每次根据v对a排序时,只会动上一次结果v相同的组别中的a。初始化a其实增加了一些交换次数,是没有必要的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值