AGC007F Shik and Copying String
涉及知识点:Ad-hoc,贪心
题意
给出两个长度相同的字符串 ,定义一次操作为:
从头至尾处理每一位,每位可以变成上一位,或者不变。
求最少对 进行多少次操作使得 。
思路
引理
可以发现,一次操作其实类似于选择一些点 ,从左到右覆盖它后面的点,并且如果记每个 覆盖的区间为 ,满足这些区间无交集。
因此我们可以想到,将 中按相邻相同的字符划分成一些段,称这段为 并且段内所有字符都为 ,显然让这些字符由离 最近的满足 的 覆盖而来是最优的,下文我们把这样的 称为该段的 ,另外下文有时用某个 来指代覆盖某段的操作。
初始思路
于是我们想到了一个做法:先将 分段,然后倒序遍历 上每个段,对于每个段找到既小于该段段首也小于前一段 的该段 。如果找到最后发现有些段找不到合法 了则说明无解。至于如何计算最少操作次数,可以记录下每段 对应在 上存在的全局 数量;此外如果 ,那么说明它不能和左边的操作共存于一轮,还要 ,取个 作为答案。
这样是错误的。
改进思路
我们仔细思考刚才的思路有什么不妥之处:
-
将 视为静态。我们统计的时候将每段的 都看成了静态的,但是实际上在一轮操作中, 可以通过操作而覆盖到 的前面(当然同时得保证 ),由于这么做不会覆盖到其他 ,因此不会影响合法性,但这样这个 就会离 更近,这样一定是更优的。
Example:
考虑
abxxcdxxx
,aaabbbbcd
,根据我们之前的计算方法,答案应该为 4,即d
、c
、b
、a
依次覆盖。但我们发现实际上不用这么做,在第一轮操作时就可以同时操作d
和b
得到abbbcdddd
,然后再同时操作a
和c
得到aaabccccd
,最后操作b
得到aaabbbbcd
,共 3 步。 -
漏掉仍有冲突的 。我们只统计了与 有冲突的 ,并且想着如果让所有 依次执行就不会有冲突,但是却没考虑 在执行的过程中有可能会和更右边的 发生冲突。
Example:
考虑
abcdef
,aabcde
,每个 区间内都只有至多 2 个 ,但很明显不只是 2 个 有冲突。
我们现在求每个段变为不影响它右边操作合法性,且不与它左边其他操作冲突的最小操作数,那么答案便为这些操作数的最大值。考虑用一个队列维护当前段 会产生影响的 ,从右到左逆序插入。
于是当我们逆序遍历到 时,处理分为两步:对 右边的 的处理,以及对 本身的处理。
右侧的处理
此时 还未插入队列。
可以证明如果队头在 右侧且到 的距离大于队列中的元素个数,那么队头的操作合法性不受该段的影响,可以弹出。用代码表示即:while(!q.empty() && q.front()-(int)q.size()+1>l) q.pop();
。
为什么?我们想象在段首设立一个“缓冲区”(我们队列维护的就是这个缓冲区),可以通过一系列操作把对该段以及该段右侧有影响的 都搬到“缓冲区”里,并且保证他们的相对顺序不变。这样一来,这些之前在该段左侧的操作全部可以合法的转移到该段缓冲区中,这保证了这些操作不与左侧其他操作冲突;另外由于相对顺序不变,那么也不影响这些右侧操作的合法性。
而如果之前入队的某些 在 右侧且到 的距离大于队列中的元素个数,那么这些 即使不用缓冲区也可以保证上述两条性质,因此不用计入缓冲区。
为什么不用计入缓冲区?
如下图, 就是这样“不用记入”的 ,因为不管它向后拓展与否,它都不影响左边其他的 进入缓冲区,但 就是个反例,如果它不动,那么 就无法进入缓冲区,进而 就必须无法拓展到 上,对左侧造成了影响。
那这样的 造成了贡献吗?
是有贡献的,但是它的操作可以与加入缓冲区的 同时操作,因此没有独立的影响。事实上, 的贡献是由遍历到段 的时候计算的,此时已经计算出了 不影响 时的操作数了。更多可以看完下文 “ 本身的处理” 回来理解。
很明显,由于要保证相对顺序,上文中“一系列操作”的次数便为“缓冲区”的大小。
本身的处理
此时 已经插入队列。
根据定义, 不可能大于 。
-
的情况:
此时说明本段的 会影响左侧的操作,因此为了满足 不与左侧操作冲突,除了做完队列中的操作,还需要左侧额外覆盖一次多余的部分,如图,先覆盖蓝色再覆盖绿色:
-
的情况:
此时说明本段的 不会影响左侧的操作,那么直接记录为“缓冲区”的大小即可。另外需特别注意如果 不在本段内,即本段完全不影响右边的 ,不能直接记为该段操作数为 ,还得判断该段 和 是否相同,如果相同则操作数只需要 。
实际上,对于 且 不在本段内的情况(代码 48~50 行)不需要额外处理,因为这个情况在遍历到前一段 的时候就由处理 达到了等价的效果。
代码
本题思路比较抽象,很难保证能一遍理解,建议思路分析与代码结合食用。
#include<bits/stdc++.h>
#define mkp make_pair
using namespace std;
typedef pair<int,int> pii;
const int MAXN=3e6+5;
int n;
string s,t;
int fst[MAXN],ans=0;
int main(){
ios::sync_with_stdio(false);
cin>>n>>s>>t;
if(s==t){
cout<<0<<endl;
return 0;
}
s=" "+s;t=" "+t;
int rside=n;
for(int i=n;i>=1;i--){
if(t[i]!=t[i+1]){
fst[rside]=i+1;
rside=i;
}
}
fst[rside]=1;
queue<int>q;
for(int l,r=n,pre=n,lstpre=n+1;r>=1;r=l-1){
l=fst[r];
if(pre>=l) pre=l;
while(pre>=1 && s[pre]!=t[l]) pre--;
if(pre<1){cout<<-1<<endl;return 0;}
while(!q.empty() && q.front()-(int)q.size()+1>l) q.pop();
q.push(pre);
if(pre<l) ans=max(ans,(int)q.size()+1);
else if(pre==l){
bool flag=false;
for(int i=l;i<=r;i++){
if(s[i]!=t[i]){
flag=true;break;
}
}
if(flag) ans=max(ans,1);
if(lstpre>r){//lstpre==r+1
ans=max(ans,(int)flag);
}
else{
ans=max(ans,(int)q.size());
}
}
lstpre=pre;
}
cout<<ans<<endl;
return 0;
}
本文作者:MessageBoxA
本文链接:https://www.cnblogs.com/SkyNet-PKN/p/18409285
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步