AGC007F Shik and Copying String

news/2024/11/15 4:45:20/文章来源:https://www.cnblogs.com/SkyNet-PKN/p/18409285

涉及知识点:Ad-hoc,贪心

题意

Link

给出两个长度相同的字符串 \(S,T\),定义一次操作为:

从头至尾处理每一位,每位可以变成上一位,或者不变。

求最少对 \(S\) 进行多少次操作使得 \(S=T\)

思路

引理

可以发现,一次操作其实类似于选择一些点 \(l\),从左到右覆盖它后面的点,并且如果记每个 \(l_i\) 覆盖的区间为 \([l_i,r_i]\),满足这些区间无交集。

因此我们可以想到,将 \(T\) 中按相邻相同的字符划分成一些段,称这段为 \([l_i,r_i]\) 并且段内所有字符都为 \(T_{l_i}\),显然让这些字符由离 \(l_i\) 最近的满足 \(S_j=T_{l_i},j\leq l_i\)\(S_j\) 覆盖而来是最优的,下文我们把这样的 \(j\) 称为该段的 \(pre\),另外下文有时用某个 \(pre\) 来指代覆盖某段的操作

初始思路

于是我们想到了一个做法:先将 \(T\) 分段,然后倒序遍历 \(T\) 上每个段,对于每个段找到既小于该段段首也小于前一段 \(pre\) 的该段 \(pre\)。如果找到最后发现有些段找不到合法 \(pre\) 了则说明无解。至于如何计算最少操作次数,可以记录下每段 \([pre_i,r_i]\) 对应在 \(S\) 上存在的全局 \(pre\) 数量;此外如果 \(pre_i<l_i\),那么说明它不能和左边的操作共存于一轮,还要 \(+1\),取个 \(\max\) 作为答案。

这样是错误的。

改进思路

我们仔细思考刚才的思路有什么不妥之处:

  1. \(pre\) 视为静态。我们统计的时候将每段的 \(pre\) 都看成了静态的,但是实际上在一轮操作中,\(S_{pre_i}\) 可以通过操作而覆盖到 \(pre_{i+1}\) 的前面(当然同时得保证 \(pre_i\leq l_i\)),由于这么做不会覆盖到其他 \(pre\),因此不会对影响合法性,但这样这个 \(pre_i\) 就会离 \(l_i\) 更近,这样一定是更优的。

    Example:

    考虑 \(S=\) abxxcdxxx\(T=\) aaabbbbcd,根据我们之前的计算方法,答案应该为 4,即 dcba 依次覆盖。但我们发现实际上不用这么做,在第一轮操作时就可以同时操作 db 得到 abbbcdddd,然后再同时操作 ac 得到 aaabccccd,最后操作 b 得到 aaabbbbcd,共 3 步。

  2. 漏掉仍有冲突的 \(pre\)我们只统计了与 \(pre_i\) 有冲突的 \(pre_j\),并且想着如果让所有 \(pre_j\) 依次执行就不会有冲突,但是却没考虑 \(pre_j\) 在执行的过程中有可能会和更右边的 \(pre\) 发生冲突。

    Example:

    考虑 \(S=\) abcdef\(T=\) aabcde,每个 \([pre_i,r_i]\) 区间内都只有至多 2 个 \(pre\),但很明显不只是 2 个 \(pre\) 有冲突。


我们现在求每个段变为不影响它右边操作合法性,且不与它左边其他操作冲突的最小操作数,那么答案便为这些操作数的最大值。考虑用一个队列维护当前段 \([l_i,r_i]\) 会产生影响的 \(pre_j\) \((i\leq j)\),从右到左逆序插入。

于是当我们逆序遍历到 \(i\) 时,处理分为两步:对 \(pre_i\) 右边的 \(pre\) 的处理,以及对 \(pre_i\) 本身的处理。

\(pre_i\) 右侧的处理

此时 \(pre_i\) 还未插入队列。

可以证明如果队头在 \(l_i\) 右侧且到 \(l_i\) 的距离大于队列中的元素个数,那么队头的操作合法性不受该段的影响,可以弹出。用代码表示即:while(!q.empty() && q.front()-(int)q.size()+1>l) q.pop();

为什么?我们想象在段首设立一个“缓冲区”(我们队列维护的就是这个缓冲区),可以通过一系列操作把对该段以及该段右侧有影响的 \(pre\) 都搬到“缓冲区”里,并且保证他们的相对顺序不变。这样一来,这些之前在该段左侧的操作全部可以合法的转移到该段缓冲区中,这保证了这些操作不与左侧其他操作冲突;另外由于相对顺序不变,那么也不影响这些右侧操作的合法性

而如果之前入队的某些 \(pre\)\(l_i\) 右侧且到 \(l_i\) 的距离大于队列中的元素个数,那么这些 \(pre\) 即使不用缓冲区也可以保证上述两条性质,因此不用计入缓冲区。

为什么不用计入缓冲区?

如下图,\(pre[i+3]\) 就是这样“不用记入”的 \(pre\),因为不管它向后拓展与否,它都不影响左边其他的 \(pre[i+1],pre[i+2]\) 进入缓冲区,但 \(pre[i+2]\) 就是个反例,如果它不动,那么 \(pre[i+1]\) 就无法进入缓冲区,进而 \(pre[i]\) 就必须无法拓展到 \(l_i\) 上,对左侧造成了影响。

那这样的 \(pre\) 造成了贡献吗?

\(pre[i+3]\) 是有贡献的,但是它的操作可以与加入缓冲区的 \(pre\) 同时操作,因此没有独立的影响。事实上,\(pre[i+3]\) 的贡献是由遍历到段 \([l_{i+3},r_{i+3}]\) 的时候计算的,此时已经计算出了 \(pre[i+3]\) 不影响 \(pre[i]\) 时的操作数了。更多可以看完下文 “\(pre_i\) 本身的处理” 回来理解。

很明显,由于要保证相对顺序,上文中“一系列操作”的次数便为“缓冲区”的大小。

\(pre_i\) 本身的处理

此时 \(pre_i\) 已经插入队列。

根据定义,\(pre_i\) 不可能大于 \(l_i\)

  • \(pre_i < l_i\) 的情况:

    此时说明本段的 \(pre\) 会影响左侧的操作,因此为了满足 \(pre_i\) 不与左侧操作冲突,除了做完队列中的操作,还需要左侧额外覆盖一次多余的部分,如图,先覆盖蓝色再覆盖绿色:

  • \(pre_i=l_i\) 的情况:

    此时说明本段的 \(pre\) 不会影响左侧的操作,那么直接记录为“缓冲区”的大小即可。另外需特别注意如果 \(pre_{i+1}\) 不在本段内,即本段完全不影响右边的 \(pre\),不能直接判断该段操作数为 \(1\),还得判断该段 \(S\)\(T\) 是否相同,如果相同则操作数只需要 \(0\)

    实际上,对于 \(pre_i=l_i\)\(pre_{i+1}\) 不在本段内的情况(代码 48~50 行)不需要额外处理,因为这个情况在处理 \(pre_{i+1}\) 的时候就由上一种情况处理过了。

代码

本体思路比较抽象,很难保证一遍理解,建议代码与分析结合食用。

#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(){freopen("string.in", "r", stdin);freopen("string.out", "w", stdout);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+1ans=max(ans,(int)flag);}else{ans=max(ans,(int)q.size());}}lstpre=pre;}cout<<ans<<endl;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/795740.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

全网最适合入门的面向对象编程教程:47 Python函数方法与接口-回调函数Callback

回调函数是编程中一种非常常见的模式,用于将函数作为参数传递给其他函数或方法。这种模式在 Python 中广泛应用于事件处理、异步编程、函数式编程等场景。全网最适合入门的面向对象编程教程:47 Python 函数方法与接口-回调函数 Callback摘要: 回调函数是编程中一种非常常见的…

如何把一个吃灰的 Kindle 设置成一个墨水屏幕的时钟 All In One

如何把一个吃灰的 Kindle 设置成一个墨水屏幕的时钟 All In One Kindle 电子书如何把一个吃灰的 Kindle 设置成一个墨水屏幕的时钟 All In OneKindle 电子书https://www.bilibili.com/video/BV1nY4y1e7gC/?t=375 demosKindle Paperwhite 7 电子书阅读器(4GB)Kindle Paperwhi…

基于小波神经网络的数据分类算法matlab仿真

1.程序功能描述 基于小波神经网络的数据分类算法。输入为5个特征值,输出为判断(是,否)。拿50组数据对本算法作为训练组,后30组数据作为验证组。这里,我们首先调用数据,然后对50组数据进行训练,然后对30组数据进行识别测试。 2.测试软件版本以及运行结果展示MATL…

基于GWO灰狼优化的CNN-LSTM的时间序列回归预测matlab仿真

1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2022a3.部分核心程序 (完整版代码包含详细中文注释和操作步骤视频)LR = Alpx(1); numHiddenUnits1 = floor(Alpx(2))+1;% 定义隐藏层中LSTM单元的数量 numHiddenUnits2 = floor(Alpx(3)…

代码整洁之道--读书笔记(8)

代码整洁之道简介: 本书是编程大师“Bob 大叔”40余年编程生涯的心得体会的总结,讲解要成为真正专业的程序员需要具备什么样的态度,需要遵循什么样的原则,需要采取什么样的行动。作者以自己以及身边的同事走过的弯路、犯过的错误为例,意在为后来者引路,助其职业生涯迈上更…

java学习9.11

IDEA里导入mybatis,lombok等依赖成功连接好数据库。 并将MYSQL数据库同时用navicat连接便利创建表等操作。设计实体类student并在数据库设计好表接下来就是如何实现在数据库里的增删改查 设计接口类mapper,将操作简化最后能正常运行实现功能。 总的来说这一块内容,刚开始学,…

LOJ4222 「IOI2024」马赛克上色 题解

题目描述 给定长为 \(n\) 、下标从零开始的 \(01\) 序列 \(x,y\) ,保证 \(x_0=y_0\) 。 令 \(col_{0,j}=x_j,col_{i,0}=y_i\) ,对 \(\forall 1\le i\lt n,1\le j\lt n\) , \(col_{i,j}=[col_{i-1,j}=0\and col_{i,j-1}=0]\) 。 \(q\) 次询问,给定 \(u,d,l,r\) ,求 \(\sum_…

2024 必收藏✨免费手机铃声下载网站大公开

最推荐的方式,当然是在线网站了。1.北岛铃声网 https://www.beidaols.cn/优点:免费下载:用户可以免费获取各种手机铃声,无需支付费用。 铃声多样:提供了丰富的铃声选择,满足不同用户的个性化需求。 教程详细:提供了详细的设置苹果铃声的教程,对于不熟悉苹果手机设置的…

原神蒙德-Typora模板

基于Newsprint主题开发的一种Typora模板新建模板 打开Typora - 文件 - 偏好设置 - 外观 - 打开主题文件夹 找到其中的 Newsprint (应该有一个文件夹+一个css,都要) ,拷贝副本,重命名(我命名的是“custom”),一定要这一步,不然后期更新的时候会覆盖 修改 打开其中的 cu…

学习日历-2024/9/11

LinkList集合 底层数据结构时双链表,查询慢,首尾操作的速度是极快的,所以多了很多的首尾操作在使用迭代器或者增强for循环遍历集合的过程中,不要使用集合的方法去添加或者删除元素即可 SQL DML-添加数据 1.给指定字段添加数据 INSERT INTO 表名(字段名1,字段名2,...) VALUES(值…

真实Redmi note11t pro澎湃刷机

解决:澎湃如何刷回miui13-14系统 澎湃bl解锁后结合视频进行刷机 刷机包下载地址 刷机工具1. 备份 首先先备份当前资料小米云备份使用小米手机助手备份资料2. 澎湃bl解锁 手机打开开发者 ​​ 连接电脑usb文件传输模式 ‍ 打开澎湃bl解锁 ,双击 点我开始解锁.bat 运行以后手机会…

[python][selenium] Web UI自动化切换iframe框架以及浏览器操作切换窗口和处理弹窗

分两部分:一、页面切换iframe框架     二、浏览器操作:切换窗口、处理弹窗一、页面切换iframe框架 3种iframe的切换方法: 1、切换iframe的方法:switch_to.frame  入参有4种:  1.1、id  1.2、name  1.3、index索引  1.4、iframe元素对象 2、返回主文档(最外…