题面

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. python中collections.OrderedDict()

    import collections #from collections import OrderededDict my_orderDict=collections.OrderedDict(house ...

  2. Amazon 消息订阅对接

    亚马逊的api 谁用谁知道...... 除了坑还是坑 头疼一周整出来,分享给铁汁们 amazon 的订阅思维,我只能说外国人脑回路有点长 下面就讲讲具体流程步骤: 第一步: 参照官方教程:设置通知(A ...

  3. 开发工具-在线JSON相关的工具

    更新记录: 2022年6月7日 新增链接. 2022年6月1日 开始. https://www.sojson.com/json2entity.html URL参数互转JSON https://tool ...

  4. Spring Data JPA系列2:SpringBoot集成JPA详细教程,快速在项目中熟练使用JPA

    大家好,又见面了. 这是Spring Data JPA系列的第2篇,在上一篇<Spring Data JPA系列1:JDBC.ORM.JPA.Spring Data JPA,傻傻分不清楚?给你个 ...

  5. JavaScript写倒计时

    在网页中,特别是电商网站中,倒计时的出现频率很高,接下来给大家介绍一下怎么用JavaScript写一个倒计时.代码如下: 首先我们通过Date构造函数的方法创建一个倒计时的结束的时间.并将其转换为毫秒 ...

  6. UiPath存在图像Image Exists的介绍和使用

    一.Image Exists的介绍 检查是否在指定的UI元素中找到图像,输出的是一个布尔值 二.Image Exists在UiPath中的使用 1. 打开设计器,在设计库中新建一个Sequence,为 ...

  7. python小题目练习(13)

    题目:封装用户的上网行为 实现代码: """Author:mllContent:封装用户的上网行为Date:2020-01-19"""def ...

  8. python小题目练习(十一)

    题目:大乐透号码生成器 需求:使用Random模块模拟大乐透号码生成器,选号规则为:前区在1 ~ 35的范围内随机产生不重复 的5个号码,后区在1~ 12的范围内随机产生不重复的2个号码.效果如图8. ...

  9. 鼠标右键打开powershell

    不需要更改配置文件什么的. 在桌面空白处按住Shift键同时鼠标右击,看看是不是就有了呢.

  10. NC201605 Bits

    NC201605 Bits 题目 题目描述 Nancy喜欢做游戏! 汉诺塔是一个神奇的游戏,神奇在哪里呢? 给出 \(3\) 根柱子,最开始时 \(n\) 个盘子按照大小被置于最左的柱子. 如果盘子数 ...