KMnO4y_Fish's Blog

返回

题目链接:CF936C Lock Puzzle


题目描述#

探险家发现了一个保险箱,里面有大量的宝藏。

保险箱上有一个密码锁,初始时显示的是一个长度为nn的小写字母字符串ss。探险家发现,当密码锁上显示的是字符串tt时,这个密码锁就会打开。

密码锁显示的字符串可以通过形如shift x的指令改变。要执行这个指令,探险家需要在00nn的范围内(包含00nn)选择一个xx。此时,设屏幕上显示的字符串p=αβp = \alpha\beta(其中β\beta的长度为xx),那么这个字符串会变为βRα\beta^{R}\alphaβR\beta^{R}表示β\beta反转后的结果)。

比如,如果屏幕上当前显示abcacbabcacb,那么执行shift 4后屏幕上会显示bcacabbcacab,因为α=ab\alpha=abβ=cacb\beta=cacbβR=baca\beta^{R}=baca

探险家担心如果执行了太多了shift操作,这个密码锁就会永远锁定。因此,他会给你nn和字符串sstt,并且请你给出一个步骤数不大于61006100的解锁方案。请注意无需最小化步骤数。

输入格式#

第一行一个整数nn,为字符串sstt的长度。

随后两行分别输入小写字母构成的字符串sstt,表示初始时屏幕显示的字符串以及解锁前需要得到的字符串。

输出格式#

如果不可能打开密码锁,输出-1

否则,第一行输出一个整数k (0k6100)k\ (0\leq k \leq 6100),表示需要的操作数量。第二行输出kk个空格隔开的整数xi (0xin)x_i\ (0\leq x_i \leq n),其中xix_i代表执行操作shift xishift\ x_i

数据范围#

1n20001 \leq n \leq 2000


无解情况#

显然,只有当ss构成的可重集合与tt不同时,问题是无解的。

设计递归#

假设当前屏幕上字符串(此后称为pp)前缀是abcabc,我们考虑再将两个指定的字符xxyy加进前缀。

手玩一下,很容易就可以发现一个非常好的做法:

------ Add 2 chars ------
abc....x..... Find x
       ~~~~~~ Step 1
.....xabc....
         ~~~~ Step 2
..y......xabc Find y
   ~~~~~~~~~~ Step 3
cbax........y
            ~ Step 4
ycbax........
plaintext

此时前缀变成了ycbax{\color{red}y}cba{\color{red}x}

整理一下信息,我们发现,若要在pp的前缀上构造出长度为len (len2)len\ (len \geq 2)的字符串t1[0..len1]t_1[0..len-1],那么只要先在pp的前缀上构造出t1[1..len2]Rt_1[1..len-2]^{R},然后再令x=t1[len1]x=t_1[len-1]y=t1[0]y=t_1[0],执行上述步骤即可。这样,我们就可以递归解决问题。

边界条件#

不难发现,以上递归不能处理的情况是len=0len=0len=1len=1

对于len=0len=0,无需执行任何操作,直接返回即可。

对于len=1len=1,我们要将t1[0]t_1[0](下面称之为xx)字符放到pp开头。使用下面方法即可:

------ Get init char ------
......x..... Find x
       ~~~~~ Step 1
...........x
           ~ Step 2
x...........
plaintext

分析步骤数#

如果nn是奇数,那么需要先使用22步来将第一个字符放到pp开头(边界条件),然后随后还要使用4×((n1)/2)4 \times ((n-1)/2)步进行递归构造。nn最大可以取到19991999,那么步骤数是40004000,完全没问题。

如果nn是偶数,只需要使用4×(n/2)4 \times (n/2)步进行递归构造。nn最大可以取到20002000,步骤数是40004000,同样没问题。

分析复杂度#

只论递归的话,复杂度O(n)O(n)

如果考虑使用暴力算法进行shiftshift操作和字符查找,总时间复杂度为O(n2)O(n^2)。而时限有22秒,因此O(能过)O(\text{能过})

具体实现#

可以发现,递归时要求构造为前缀的字符串,要么是tt的子串,要么是tt的子串反转。

设计递归函数时,传入三个参数l,r,d,其中dd11时表示是子串,dd1-1时表示子串反转。如果d=1d=1,那么子串区间为[l..r][l..r],否则为[r..l][r..l]

这种设计比较容易实现。

代码#

Talk is cheap, show me the codeTalk\ is\ cheap,\ show\ me\ the\ code

// status: [Accepted]
// oj:     [luogu]

#include<bits/stdc++.h>
using namespace std;

/*

------ Get init char ------
......x.....    int idx = lastIndexOf(x);
       ~~~~~    do_shift(n-idx-1);
...........x
           ~    do_shift(1);
x...........


------ Add 2 chars ------
abc....x.....   int idx = lastIndexOf(x);
       ~~~~~~   do_shift(n-idx);
.....xabc....
         ~~~~   do_shift(idx-len+2);
..y......xabc   idx = indexOf(y);
   ~~~~~~~~~~   do_shift(n-idx-1);
cbax........y
            ~   do_shift(1);
ycbax........

*/

int n;
string s;
string t;
// 存储答案 
vector<int> ans;

// 进行shift操作,并记录到答案中 (此处直接在s串上操作) 
void do_shift(int x) {
    ans.push_back(x);
    
    string tmp = s.substr(s.length()-x);
    s = string(x,' ') + s.substr(0,s.length()-x);
    
    for(unsigned i=0;int(i)<x;i++) {
        s[x-i-1] = tmp[i];
    }
}

// 检查能否完成任务。如果能,正常返回,否则直接输出-1并结束程序 
void test() {
    int st[26];
    for(int i=0;i<26;i++) {
        st[i] = 0;
    }
    for(int i=0;i<n;i++) {
        st[s[i]-'a'] ++;
        st[t[i]-'a'] --;
    }
    for(int i=0;i<26;i++) {
        if(st[i]) {
            cout<<-1<<endl;exit(0);
        }
    }
    return;
}

// 将t的子串或子串反转形式构造为s的前缀(此处直接在s串上操作) 
void buildString(int l,int r,int d) {
    int len = (r-l)*d+1;
    
    // 边界判断 
    if(len==0) return;
    if(len==1) {
        int idx = s.find_last_of(t[l]);
        // 直接忽略掉x==0的shift操作 
        if(n-idx-1 > 0) do_shift(n-idx-1);
        do_shift(1);
        return;
    }
    
    // 递归构造 
    buildString(r-d,l+d,-d);
    
    // 加入2个新的字符 
    char x = t[r];
    char y = t[l];
    
    int idx = s.find_last_of(x);
    do_shift(n-idx);
    if(idx-len+2 > 0) do_shift(idx-len+2);
    idx = s.find(y);
    do_shift(n-idx-1);
    do_shift(1);
    return;
}

// 输出最终答案 
void print() {
    cout<<ans.size()<<'\n';
    for(unsigned i=0;i<ans.size();i++) {
        if(i) cout<<' ';
        cout<<ans[i];
    }
    cout<<endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin>>n>>s>>t;
    
    // 测试能否完成 
    test();
    
    // 构造 
    buildString(0,n-1,1);
    
    // 该函数在s!=t时会RE,用于在调试时快速判断是否写挂 
    assert(s==t);
    
    // 输出结果 
    print();
}
cpp

评测记录:R24483982

[洛谷题解] CF936C Lock Puzzle
https://ak-ioi.com/blog/algo-solution/luogu-cf936c
作者 KMnO4y_Fish
发布于 2019年9月29日
评论似乎无法加载。试试刷新?✨