题面

Alice 和 Bob 最近热衷于玩一个游戏——积木小赛。

Alice 和 Bob 初始时各有 n 块积木从左至右排成一排,每块积木都被标上了一个英文小写字母。

Alice 可以从自己的积木中丢掉任意多块(也可以不丢);Bob 可以从自己的积木中丢掉最左边的一段连续的积木和最右边的一段连续的积木(也可以有一边不丢或者两边都不丢)。两人都不能丢掉自己所有的积木。然后 Alice 和 Bob 会分别将自己剩下的积木按原来的顺序重新排成一排。

Alice 和 Bob 都忙着去玩游戏了,于是想请你帮他们算一下,有多少种不同的情况下他们最后剩下的两排积木是相同的。

两排积木相同,当且仅当这两排积木块数相同且每一个位置上的字母都对应相同。两种情况不同,当且仅当 Alice(或者 Bob)剩下的积木在两种情况中不同。

【输入格式】

从文件 block.in 中读入数据。
第一行一个正整数 n,表示积木的块数。
第二行一个长度为 n 的小写字母串 s,表示 Alice 初始的那一排积木,其中第 i 个
字母 si 表示第 i 块积木上的字母。
第三行一个长度为 n 的小写字母串 t,表示 Bob 初始的那一排积木,其中第 i 个字
母 ti 表示第 i 块积木上的字母。

【输出格式】

输出到文件 block.out 中。
一行一个非负整数表示答案。

【数据范围与提示】

对于所有测试点:1 ≤ n ≤ 3000,s 与 t 中只包含英文小写字母。
测试点 1 满足:n ≤ 3000,s 与 t 中只包含同一种字母。
测试点 2 ∼ 4 满足:n ≤ 100。
测试点 5 ∼ 7 满足:n ≤ 500。
测试点 8 ∼ 10 满足:n ≤ 3000。

题解

这题的 n 如此小,我们可以暴力枚举 t 中每个子串。怎么判断该子串是否是 s 的子序列且之前没出现过呢?

首先,判断是否是 s 的子序列,我们可以构建一个子序列自动机,即拿一个字符串在上面跑,能跑到 s 的本质不同的每个子序列,类似跑子串的后缀自动机,只不过它更简单。原理:每个点的 sonc 存它后面第一个字符 c 所在的位置,基于贪心的思路,一定可以保证子序列都能跑到。构建子序列自动机极其简单,只需要倒着跑一遍字符串,当前点继承上一个点,并更新每个字符最近的位置就行,复杂度 O(n*字符集大小):

for(int i = len-1;i > 0;i --) A[i]=A[i+1],A[i].son[s[i+1]-'a']=i+1;

然后要判断 t 的当前子串是否之前出现过。哈希判断会多一个 log ,官方题解里并没加上。虽然可以过,但是不是很优(不然这篇文章意义何在?肯定是官方题解里没有的才讲)。于是我们可以对 t 建一个后缀自动机,然后拿 t 的子串(从左到右的顺序)在上面匹配,同时在 s 的子序列自动机上跑,判断有无在 s 中出现。后缀自动机和 Parent Tree 高度统一,因此,我们判断该子串合法时,为了去重,我们在后缀自动机上的对应节点(此时 SAM 上一定跑到了一个点的)存第一次出现的右端位置。因为 Parent Tree 相当于前缀树,所以要存右端点,若当前跑到这个节点时,已经存的有比它的右端点小的位置,那么说明在之前该子串出现过(根据 endpos 的定义),就不计数。若等于它的右端点,说明它出现在之前某个合法串的后缀中,还是要计数的。

于是,我们就得到了一个 O(26 n + n2) = O(n2) 的算法,比官方题解优(用哈希+基排的当我没说)。

CODE

考场代码,未经修饰:

#include<set>
#include<map>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 3005
#define MAXC 26
#define DB double
#define LL long long
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
LL read() {
LL f = 1,x = 0;char s = getchar();
while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
return f * x;
}
int n,m,i,j,s,o,k;
struct SAM{
int s[MAXC+2];
int fa,len;
SAM(){fa=len=0;memset(s,0,sizeof(s));}
}sam[MAXN<<1],sub[MAXN];
int las = 1,cnt = 1;
int pos[MAXN<<1];
void addsam(int c) {
int p = las;int np = (las = ++ cnt);
sam[np].len = sam[p].len + 1;
for(;p && !sam[p].s[c];p = sam[p].fa) sam[p].s[c] = np;
if(!p) sam[np].fa = 1;
else {
int q = sam[p].s[c];
if(sam[q].len == sam[p].len + 1) sam[np].fa = q;
else {
int nq = ++ cnt; sam[nq] = sam[q];
sam[nq].len = sam[p].len + 1;
sam[np].fa = sam[q].fa = nq;
for(;p && sam[p].s[c] == q;p = sam[p].fa) sam[p].s[c] = nq;
}
}
return ;
}
char s1[MAXN],s2[MAXN];
int main() {
freopen("block.in","r",stdin);
freopen("block.out","w",stdout);
n = read();
scanf("%s",s1 + 1);
scanf("%s",s2 + 1);
for(int i = 1;i <= n;i ++) {
addsam(s2[i]-'a');
}
for(int i = n-1;i >= 0;i --) {
sub[i] = sub[i+1];
sub[i].s[s1[i+1]-'a'] = i+1;
}
int ans = 0;
for(int i = 1;i <= n;i ++) {
int p1 = 0,p2 = 1;
for(int j = i;j <= n;j ++) {
int cc = s2[j]-'a';
if(!sub[p1].s[cc]) break;
if(!sam[p2].s[cc]) break;
p1 = sub[p1].s[cc];
p2 = sam[p2].s[cc];
if(pos[p2] == 0) pos[p2] = j;
if(pos[p2] >= j) ans ++;
}
}
printf("%d\n",ans);
return 0;
}
/*
20
egebejbhcfabgegjgiig
edfbhhighajibcgfecef
*/

CCF NOI Online 2021 提高组 T2 积木小赛 (子序列自动机+后缀自动机,O(n^2))的更多相关文章

  1. CCF NOI Online 2021 提高组 赛后心得

    T1 做个,不会,拿到 20 pts 跑路. 注意后面有个 K = 1 的部分分,这个可以递推求 b 的个数,然后直接乘上 a0 . 官方正解讲得极其详细,我还是第一次见到可以 O(K2) 做 1~n ...

  2. CCF NOI Online 2021 提高组 T3 岛屿探险(CDQ 分治,Trie 树)

    题面 凇睦是一个喜欢探险的女孩子,这天她到一片海域上来探险了. 在这片海域上一共有 n 座岛屿排成一排,标号为 1, 2, 3, . . . , n.每座岛屿有两个权值,分别为劳累度 ai 和有趣度 ...

  3. [NOI Online 2021 提高组] 积木小赛

    思路不说了. 想起来自己打比赛的时候,没睡好.随便写了个\(HASH\),模数开小一半分都没有. 然后学了\(SAM\),发现这个判重不就是个水题. \(SAM\)是字串tire的集合体. 随便\(d ...

  4. luogu P6570 [NOI Online #3 提高组]优秀子序列 二进制 dp

    LINK:P6570 [NOI Online #3 提高组]优秀子序列 Online 2的T3 容易很多 不过出于某种原因(时间不太够 浪了 导致我连暴力的正解都没写. 容易想到 f[i][j]表示前 ...

  5. JZOJ2020年8月11日提高组T2 宝石

    JZOJ2020年8月11日提高组T2 宝石 题目 Description 见上帝动了恻隐之心,天后也想显示一下慈悲之怀,随即从口袋中取出一块魔术方巾,让身边的美神维纳斯拿到后堂的屏风上去试试,屏风是 ...

  6. 【GDKOI2014】JZOJ2020年8月13日提高组T2 石油储备计划

    [GDKOI2014]JZOJ2020年8月13日提高组T2 石油储备计划 题目 Description Input Output 对于每组数据,输出一个整数,表示达到"平衡"状态 ...

  7. 【五校联考1day2】JZOJ2020年8月12日提高组T2 我想大声告诉你

    [五校联考1day2]JZOJ2020年8月12日提高组T2 我想大声告诉你 题目 Description 因为小Y 是知名的白富美,所以自然也有很多的追求者,这一天这些追求者打算进行一次游戏来踢出一 ...

  8. 【NOIP2015模拟11.5】JZOJ8月5日提高组T2 Lucas的数列

    [NOIP2015模拟11.5]JZOJ8月5日提高组T2 Lucas的数列 题目 PS:\(n*n*T*T<=10^{18}\)而不是\(10^1*8\) 题解 题意: 给出\(n\)个元素的 ...

  9. 【NOIP2015模拟11.2晚】JZOJ8月4日提高组T2 我的天

    [NOIP2015模拟11.2晚]JZOJ8月4日提高组T2 我的天 题目 很久很以前,有一个古老的村庄--xiba村,村子里生活着n+1个村民,但由于历届村长恐怖而且黑暗的魔法统治下,村民们各自过着 ...

随机推荐

  1. 并发编程原理学习:synchronized关键字

    概述 关键字synchronized可以修饰方法或者以同步代码块的形式来进行使用,它主要确保多个线程在同一时刻只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性. 同步代码块 ...

  2. 静态代理、动态代理与Mybatis的理解

    静态代理.动态代理与Mybatis的理解 这里的代理与设计模式中的代理模式密切相关,代理模式的主要作用是为其他对象提供一种控制对这个对象的访问方法,即在一个对象不适合或者不能直接引用另一个对象时,代理 ...

  3. 整理orcal常用sql语句

    1.表插入列 alter table XMJ_ONE add column1 NUMBER(38) default 0;comment on column XMJ_ONE.column1 is '字段 ...

  4. 开启apache2的ssl访问功能

    Ubuntu 20.04 1. Apache2默认安装的时候,ssl模块是不启用的.开启命令: $ sudo apt install apache2 #安装$ sudo a2enmod ssl #开启 ...

  5. 循环控制-break语句和continue语句

    break关键字的用法有常见的两种: 1.可以用switch语句当中,一旦执行,整个switch语句立刻结束 2.还可以用在循环语句当中,一定执行,整个循环语句立刻结束,打断循环 关于循环的选择,有一 ...

  6. NuGetTools:批量上传、删除和显示NuGet包

    快照 前言 NuGet是.NET开发必不可少的包管理工具,在日常更新版本过程中,可能需要批量发布 NuGet 包,也不可避免需要发布一些测试的包,后期想将这些测试或者过期的包删除掉.nuget.org ...

  7. HashMap设计原理与实现(下篇)200行带你写自己的HashMap!!!

    HashMap设计原理与实现(下篇)200行带你写自己的HashMap!!! 我们在上篇文章哈希表的设计原理当中已经大体说明了哈希表的实现原理,在这篇文章当中我们将自己动手实现我们自己的HashMap ...

  8. 索尼笔记本Linux系统唤醒后,键盘无法使用

    1.编辑grub文件 sudo gedit /etc/default/grub 2.修改成以下参数 GRUB_CMDLINE_LINUX_DEFAULT="quiet splash i804 ...

  9. springboot java -jar指定启动的jar外部配置文件

    Limited Setting Effect 中文描述 Java 8 -Xbootclasspath:<path> Sets the search path for bootstrap c ...

  10. java的Test 如何使用@Autowired注解

    1.配置来至bean.xml @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "class ...