AT2173

一道乱搞的题...

题意:给你两个字符串,每次操作步骤如下:从前向后扫描整个字符串,对于每一位都有两种选择,一种是保持原来的字符不变,另一种是把这一位上的字符变成和前一位一样(注意是操作后的前一位),问你至少需要多少次操作才能将第一个字符串变成第二个字符串,如果不可能输出-1

题解:

看到网上所有人都在说“画一画就好”,然而我这种蒟蒻根本不会画啊喂!!!

因此这篇博客的目的在于...谈一谈怎么画...

首先我们不难看到:最佳的方法一定是从后向前修改(因为如果从前向后修改,很有可能把要用的字符覆盖掉了,这样就无法成功覆盖了)

接下来,我们维护一个单调递减的变量$posi$,用于记录上一个先前最早匹配的位置在哪

可能这句话并不好理解,我们举个例子讲:

比如原串是$acbaba$,而目标串是$acbacb$,那么合理的对应方式应当是这样的:

可以看到,虽然4号位置上的$a$之前最近的就是自己上面的$a$,但是不能直接继承,因为5号位上的$c$已经把匹配位置放到2了,为了保证正确覆盖,必须用之前的$a$,这也就导致了前面的无法匹配,所以这是一个无解情况!

那么我们回到有解的情况:

如果有解的话,显然原串中的每一个点最后会覆盖到目标串上的一段区间(废话),那么我们取出这个区间的最左端点来研究即可。

不难发现,每一个值在覆盖的时候一定是一种折线的形式!

举个例子:

这是一个例子的部分情况

 可以发现:为了覆盖上那三个$a$,我们应该用原来的$a$覆盖整个区间

但是为了避免中间的位置被错误覆盖导致这之后的位置被覆盖,所以我们应该用中间的位置覆盖再去覆盖后面的位置!

举个例子:

$s="abcde",t="aaacc"$

为了防止a开始覆盖后覆盖掉了c导致后面无法被覆盖,我们要先用c去覆盖

所以针对a而言,它的覆盖过程更应该是一条折线!

或者,应该是这样:

每次向前拓展了一个部分,直到能覆盖上目标即可。

因此,我们只需要求出这样的折线最多折了几次就是答案!

怎么求?

我们开一个队列,队列里储存这样的折线中需要折的每个位置(也即一个位置对应一次偏折),那么队列的大小即为偏折的次数

然后每次将无用的位置弹出队列即可。

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
char s[1000005],t[1000005];
queue <int> Q;
int n;
bool check()
{
    for(int i=1;i<=n;i++)if(s[i]!=t[i])return 0;
    return 1;
}
int main()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    scanf("%d",&n);
    scanf("%s%s",s+1,t+1);
    if(check()){printf("0\n");return 0;}
    int posi=n;
    int ans=0;
    for(int i=n;i>=1;i--)
    {
        if(t[i]==t[i-1])continue;
        posi=min(posi,i);
        while(posi&&t[i]!=s[posi])posi--;
        if(!posi){printf("-1\n");return 0;}
        while(!Q.empty())
        {
            if((int)Q.front()-(int)Q.size()>=i)Q.pop();
            else break;
        }
        Q.push(posi);
        if(i!=posi)ans=max(ans,(int)Q.size());
    }
    printf("%d\n",ans+1);
    return 0;
}

 

posted @ 2019-05-12 19:55  lleozhang  Views(146)  Comments(0Edit  收藏  举报
levels of contents