题面

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. 第1期 考研中有关函数的一些基本性质《zobol考研微积分学习笔记》

    在入门考研微积分中,我们先复习一部分中学学的初等数学的内容.函数是非常有用的数学工具. 1.函数的性质理解: 首先考研数学中的所有函数都是初等函数.而函数的三个关键就是定义域.值域.对应关系f. 其中 ...

  2. vivo 容器集群监控系统架构与实践

    vivo 互联网服务器团队-YuanPeng 一.概述 从容器技术的推广以及 Kubernetes成为容器调度管理领域的事实标准开始,云原生的理念和技术架构体系逐渐在生产环境中得到了越来越广泛的应用实 ...

  3. SAP Context menu(菜单)

    *&---------------------------------------------------------------------* *& Report RSDEMO_CO ...

  4. VisionPro · C# · 加密狗检查程序

    写VisionPro C#项目时,我们需要在程序的启动时加载各种配置文件,以及检查软件授权,以下代码即检查康耐视加密狗在线状态,如查无加密狗,关闭程序启动进程并抛出异常. 1 using System ...

  5. java中常见的锁

    1.悲观锁 认为别的线程都会修改数据,二话不说先锁上 synchronized 2.乐观锁 乐观豁达,起初不操作.最后修改的时候比对一下版本,不一致再上锁 3.可重入锁 外层锁了之后,内层仍可以直接使 ...

  6. 用Python爬取文章,并转PDF格式电子书

    wkhtmltopdf [软件],这个是必学准备好的,不然这个案例是实现不出来的 获取文章内容代码 (https://jq.qq.com/?_wv=1027&k=QgGWqAVF) 发送请求, ...

  7. python 常用的数据类型

    常用的数据类型 整数型 -> int 可以表示正数.负数.0 整数的不同进制的表示方法 十进制->默认的进制,无需特殊表示 二进制->以0b开头 八进制->以0o开头 十六进制 ...

  8. 10.2 如何运行Android项目到Android Studio自带模拟器

    Android开发一般都可以将应用运行到模拟器查看效果,除非特殊项目要用到真机,所以我们这里先讲解如何将项目运行到模拟器,以校验我们的开发环境以及创建的项目是否有问题. 创建模拟器 点击"C ...

  9. c# 反射专题—————— 介绍一下是什么是反射[ 一]

    前言 为什么有反射这个系列,这个系列后,asp net 将会进入深入篇,如果没有这个反射系列,那么asp net的源码,看了可能会觉得头晕,里面的依赖注入包括框架源码是大量的反射. 正文 下面是官方文 ...

  10. c# SerialPort HEX there is no data received

    C#窗口程序进行串口通信,按照串口通信协议,设置com口,波特率,停止位,校验位,数据位,本地虚拟串口调试ok,但是和外设调试时,发送HEX模式数据命令,没有数据返回, 所以关键问题在于HEX模式,发 ...