BZOJ3277: 串(广义后缀自动机)
时间:2022-06-04
本文章向大家介绍BZOJ3277: 串(广义后缀自动机),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
Time Limit: 10 Sec Memory Limit: 128 MB
Submit: 1196 Solved: 478
Description
字符串是oi界常考的问题。现在给定你n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中
至少k个字符串的子串(注意包括本身)。
Input
第一行两个整数n,k。
接下来n行每行一个字符串。
n,k,l<=100000
Output
输出一行n个整数,第i个整数表示第i个字符串的答案。
Sample Input
3 1 abc a ab
Sample Output
6 1 3
HINT
Source
广义后缀自动机?就是把一坨字符串建到一个后缀自动机上??
不过好难理解啊qwq。。
对于这题,首先我们要知道几个定理
1.节点$i$表示的本质不同的字符串可以由$len[i] - len[fa[i]]$得到
2.一个串的子串 等价于 一个串所有前缀的所有后缀
这样子串就转换为求一个串的前缀的所有后缀的问题
前缀可以枚举,问题转换为求一个字符串的各个后缀在其他字符串中出现了多少次
这样我们可以把广义后缀自动机建出来,然后暴力沿着$parent$边跑,这样可以枚举出所有后缀
同时为了不重复枚举,我们需要记录下每个后缀是否已经被枚举过了
这样我们就可以知道一个状态出现的次数是否$>= K$,接下来我们只要统计出这个状态出现的次数就行了
根据定理$1$,这个很好统计
然后就做完啦
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int MAXN = 1e6 + 10;
string s[MAXN];
int N, K;
int fa[MAXN], len[MAXN], ch[MAXN][26], root = 1, last = 1, tot = 1, times[MAXN];
void insert(int x) {
int now = ++tot, pre = last; last = now; len[now] = len[pre] + 1;
for(; pre && !ch[pre][x]; pre = fa[pre]) ch[pre][x] = now;
if(!pre) fa[now] = root;
else {
int q = ch[pre][x];
if(len[q] == len[pre] + 1) fa[now] = q;
else {
int nows = ++tot; len[nows] = len[pre] + 1;
memcpy(ch[nows], ch[q], sizeof(ch[q]));
fa[nows] = fa[q]; fa[q] = fa[now] = nows;
for(; pre && ch[pre][x] == q; pre = fa[pre]) ch[pre][x] = nows;
}
}
}
int vis[MAXN], sum[MAXN];
void GetTimes() {//求出每一个状态在几个字符串出现过
for(int i = 1; i <= N; i++) {
int now = root;
for(int j = 0; j < s[i].length(); j++) {
now = ch[now][s[i][j] - 'a'];//枚举每一个前缀
int t = now;
while(t && vis[t] != i) vis[t] = i, times[t]++, t = fa[t];//枚举每一个后缀
}
}
}
void dfs(int x) {
if(x == root || vis[x]) return ;
vis[x] = 1;
dfs(fa[x]);
sum[x] += sum[fa[x]];
}
int main() {
#ifdef WIN32
freopen("a.in", "r", stdin);
#endif
ios::sync_with_stdio(0);
cin >> N >> K;
for(int i = 1; i <= N; i++) cin >> s[i];
for(int i = 1; i <= N; i++) {
last = 1;
for(int j = 0; j < s[i].length(); j++)
insert(s[i][j] - 'a');
}
GetTimes();
for(int i = 1; i <= tot; i++) sum[i] = (times[i] >= K) * (len[i] - len[fa[i]]);//i状态所表示的子串集合对答案的贡献
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= tot; i++) dfs(i);
for(int i = 1; i <= N; i++) {
int ans = 0, now = root;
for(int j = 0; j < s[i].length(); j++)
now = ch[now][s[i][j] - 'a'], ans += sum[now];
//枚举前缀,算出每一个前缀所包含的后缀对答案啊的贡献
printf("%d ", ans);
}
return 0;
}
- ASM无法启动的问题分析(一)(r7笔记第87天)
- 54. 心跳的实现 | 厚土Go学习笔记
- 53. Socket服务三次握手的示例 | 厚土Go学习笔记
- 使用Beego+Swagger构建更好的API服务
- ASM无法启动的问题分析(二)(r7笔记第88天)
- 43. 等价二叉树 | 厚土Go学习笔记
- 51. Socket服务端和客户端使用TCP协议通讯 | 厚土Go学习笔记
- 50. RESTful API的简单实现 | 厚土Go学习笔记
- go实现西瓜视频花椒直播等平台智能答题
- 主备切换的准备工作(二) (r7笔记第85天)
- 49. 访问PostgreSQL数据库增删改查 | 厚土Go学习笔记
- Golang中Interface类型详解
- Go语言的网络编程简介
- 一条关于swap争用的报警邮件分析(二)(r8笔记第4天)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 使用laravel的migrate创建数据表的方法
- 解决laravel session失效的问题
- php7下的filesize函数
- PHP-FPM 设置多pool及配置文件重写操作示例
- laravel实现登录时监听事件,添加登录用户的记录方法
- php更新cookie内容的详细方法
- php实现映射操作实例详解
- Laravel 已登陆用户再次查看登陆页面的自动跳转设置方法
- yii框架数据库关联查询操作示例
- laravel-admin的多级联动方法
- Laravel数据库读写分离配置的方法
- php给数组赋值的实例方法
- php实现分页功能的详细实例方法
- 浅谈Laravel中的三种中间件的作用
- laravel 使用auth编写登录的方法