题面

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. 编程式导航路由跳转到当前路由(参数不变), 多次执行会抛出NavigationDuplicated的警告错误?

    注意:编程式导航(push|replace)才会有这种情况的异常,声明式导航是没有这种问题,因为声明式导航内部已经解决这种问题. 这种异常,对于程序没有任何影响的. 为什么会出现这种现象: 由于vue ...

  2. supervisor的安装与使用

    Ubuntu安装使用supervisor 进程管理工具 安装 apt-get install supervisor 查看是否安装成功 pgrep supervisord # 返回进程号则成功 改配置文 ...

  3. css做旋转相册效果

    css做旋转相册效果 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> &l ...

  4. element ui 自定义主题失败(primordials is not defined)

    卸载: 1.卸载cnpm npm uninstall cnpm -g 2.卸载vue-cli npm uninstall @vue/cli -g 3.卸载nodejs和删除文件 C:\Program ...

  5. SAP Using Text Modules in Adobe Forms

    In this demo we will create an adobe form which displays text in two different languages (English or ...

  6. 【python基础】第08回 流程控制 for循环

    本章内容概要 1.循环结构之 for 循环 本章内容详解 1.循环结构之for循环 1.1 语法结构 for 变量名 in 可迭代对象: #字符串 列表 字典 元组 for 循环的循环体代码 针对变量 ...

  7. 网络通讯之Socket-Tcp(一)

    网络通讯之Socket-Tcp  分成3部分讲解: 网络通讯之Socket-Tcp(一): 1.如何理解Socket 2.Socket通信重要函数 网络通讯之Socket-Tcp(二): 1.简单So ...

  8. 全国气象数据/降雨量分布数据/太阳辐射数据/NPP净初级生产力数据/植被覆盖度数据

    ​        气象数据一直是一个价值较高的数据,它被广泛用于各个领域的研究当中.气象数据包括有气温.气压.相对湿度.降水.蒸发.风向风速.日照等多种指标,但是包含了这些全部指标的气象数据却较难获取 ...

  9. Note -「0/1 Fractional Programming」

    What is that? Let us pay attention to a common problem that we often meet in daily life: There are \ ...

  10. 总结下对我对于CSS中BFC的认知

    首先第一个,什么是BFC? BFC的全称叫Block  Formatting  Context   (块级格式化上下文)BFC是css中隐含属性,开启BFC后元素会变成一个独立的布局环. 简单来说,它 ...