知识点: 广义 SAM

原题面 Luogu


「扯」

随便「口胡」一下居然「过」了。

比较考验「代码能力」,第一次感觉「大模拟」没有白写(((

还有这个「符号」实在是太「上头」了。


前置知识

在线构造广义 SAM,推荐:【学习笔记】字符串—广义后缀自动机 - 辰星凌


题意简述

给定 \(n\) 个仅包含小写字母的字符串 \(S_1\sim S_n\)。

定义字符串 \(S_i\) 的 「独特值」为只属于该串的本质不同的非空子串的个数。

求字符串 \(S_1\sim S_n\) 的「独特值」。

\(1\le n\le 10^5, 1\le \sum|S_i|\le 10^5\)。


分析题意

算法一

多串子串问题,考虑广义 SAM。

若 \(n\) 较小,直接维护每个状态 包含几个串的信息。

parent 树上 DP 更新祖先信息,直接更新答案即可。

但 \(n\le 10^5\) 空间爆炸,做不得,考虑乱搞一波。


算法二

用 string 存字符串,建广义 SAM。

在动态建立 SAM 时,\(\operatorname{only}_i\) 记录每个状态 \(i\) 包含哪一个串的信息。

若某状态包含多个串信息,则\(\operatorname{only}_i = - 1\)。

parent 树上 DP 更新祖先信息。

先考虑一波 无脑暴力:

对于 \(S_i\),枚举其所有子串,在 SAM 上跑出对应状态 \(u\)。

若有 \(\operatorname{only}_u\not = -1\), 对应状态只维护了一个串的信息,一定为该子串。

这样的子串数,即为 \(S_i\) 的「独特值」。


上述算法瓶颈是枚举子串。发现前缀有一些好性质:

  1. 连续前缀 对应状态在 SAM 上也是连续的,实现时直接把串扔到 SAM 上跑 即得对应状态。
  2. 前缀对应状态到 parent 树根的链上 包含该前缀所有后缀,可以包含所有子串信息。

考虑仅枚举前缀,枚举复杂度变为线性。


发现一些结论:

  1. parent 树上一条从叶到根的链,维护的串的个数,是单调不减的。
  2. 若某状态 \(i\) 的 \(\operatorname{only}_i\not = -1\), 则对于其子树中所有状态 \(j\),有 \(\operatorname{only}_j=\operatorname{only}_i\)。

考虑维护状态 \(i\) 的 parent 树上距它最远的,\(\operatorname{only}\not= -1\) 的祖先 \(\operatorname{top}_i\)。

由结论 1,若某状态 \(i\) 的 \(\operatorname{only}_i=-1\),\(\operatorname{top}_i=0\)。

由结论 2,跑到的 \(\operatorname{only}\not= - 1\) 的节点对答案有贡献,在其子树中所有节点都会有贡献。

可预处理子树 \(\operatorname{len}(i)-\operatorname{len}(\operatorname{link}(i))\) 之和 \(\operatorname{sum}\),即某子树中的子串数量。

统计答案时,统计 跑到的状态 \(u\) 的 \(\operatorname{sum}(\operatorname{top}_u)\),一个 \(\operatorname{top}_u\) 只能统计一次,去重可用排序实现。

这样为什么是对的?考虑前缀的性质 2 感性理解一下。

每个串都在 SAM 上进行一匹配 并进行一次排序,总复杂度大概是 \(O(\sum\limits_{i}^{n} |S_i|\log |S_i|)\),可过。


算法三

瞅了一眼题解,发现自己做麻烦了。

DP 求得 \(\operatorname{only}\) 后,直接遍历所有状态,若存在 \(\operatorname{only}_i \not= -1\),则令 \(ans _{\operatorname{only}_i}\) 加上 \(\operatorname{len}(i)-\operatorname{len}(\operatorname{link}(i))\)即可。

复杂度 \(O(\sum|S_i|)\),少一个排序的复杂度。


爆零小技巧

前缀和 没有加 前缀的和。

你的前缀和既不前,也不缀,更不和。


代码实现

算法三

//知识点:SAM
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#define ll long long
const int kMaxn = 2e5 + 10;
const int kMaxm = 26;
//=============================================================
std :: string S[kMaxn];
int only[kMaxn << 1], ans[kMaxn];
int num, node_num = 1, ch[kMaxn << 1][kMaxm], len[kMaxn <<1], link[kMaxn << 1];
int edge_num, head[kMaxn], v[kMaxn << 1], ne[kMaxn << 1];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void AddEdge(int u_, int v_) {
v[++ edge_num] = v_, ne[edge_num] = head[u_], head[u_] = edge_num;
}
int Insert(int c_, int last_) {
if (ch[last_][c_]) {
int p = last_, q = ch[p][c_];
if (len[p] + 1 == len[q]) {
only[q] = - 1;
return q;
}
int newq = ++ node_num;
memcpy(ch[newq], ch[q], sizeof(ch[q]));
len[newq] = len[p] + 1;
link[newq] = link[q];
link[q] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
only[newq] = num;
return newq;
}
int p = last_, now = ++ node_num;
only[now] = num;
len[now] = len[p] + 1;
for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
if (! p) {link[now] = 1; return now;}
int q = ch[p][c_];
if (len[q] == len[p] + 1) {link[now] = q; return now;}
int newq = ++ node_num;
memcpy(ch[newq], ch[q], sizeof(ch[q]));
link[newq] = link[q], len[newq] = len[p] + 1;
link[q] = link[now] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
return now;
}
void Dfs1(int u_) {
for (int i = head[u_]; i; i = ne[i]) {
Dfs1(v[i]);
if (only[u_] == - 1) continue ;
if (! only[u_]) {
only[u_] = only[v[i]];
} else if (only[u_] != only[v[i]]) {
only[u_] = - 1;
}
}
}
//=============================================================
int main() {
int T = read();
for (num = 1; num <= T; ++ num) {
std :: cin >> S[num];
int n = S[num].length(), last = 1;
for (int j = 0; j < n; ++ j) last = Insert(S[num][j] - 'a', last);
}
for (int i = 2; i <= node_num; ++ i) AddEdge(link[i], i);
Dfs1(1);
for (int i = 2; i <= node_num; ++ i) {
if (only[i] != - 1) ans[only[i]] += len[i] - len[link[i]];
}
for (int i = 1; i <= T; ++ i) printf("%d\n", ans[i]);
return 0;
}

算法二

//知识点:广义 SAM
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#define ll long long
const int kMaxn = 2e5 + 10;
const int kMaxm = 26;
//=============================================================
std :: string S[kMaxn];
int only[kMaxn << 1], top[kMaxn << 1], sum[kMaxn << 1];
int num, node_num = 1, ch[kMaxn << 1][kMaxm], len[kMaxn <<1], link[kMaxn << 1];
int edge_num, head[kMaxn], v[kMaxn << 1], ne[kMaxn << 1];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void AddEdge(int u_, int v_) {
v[++ edge_num] = v_, ne[edge_num] = head[u_], head[u_] = edge_num;
}
int Insert(int c_, int last_) {
if (ch[last_][c_]) {
int p = last_, q = ch[p][c_];
if (len[p] + 1 == len[q]) {
only[q] = - 1;
return q;
}
int newq = ++ node_num;
memcpy(ch[newq], ch[q], sizeof(ch[q]));
len[newq] = len[p] + 1;
link[newq] = link[q];
link[q] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
only[newq] = num;
return newq;
}
int p = last_, now = ++ node_num;
only[now] = num;
len[now] = len[p] + 1;
for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
if (! p) {link[now] = 1; return now;}
int q = ch[p][c_];
if (len[q] == len[p] + 1) {link[now] = q; return now;}
int newq = ++ node_num;
memcpy(ch[newq], ch[q], sizeof(ch[q]));
link[newq] = link[q], len[newq] = len[p] + 1;
link[q] = link[now] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
return now;
}
void Dfs1(int u_) {
for (int i = head[u_]; i; i = ne[i]) {
Dfs1(v[i]);
if (only[u_] == - 1) continue ;
if (! only[u_]) {
only[u_] = only[v[i]];
} else if (only[u_] != only[v[i]]) {
only[u_] = - 1;
}
}
}
void Dfs2(int u_, int top_) {
if (! top_ && only[u_] != - 1) top_ = u_;
top[u_] = top_;
if (top_) sum[u_] += len[u_] - len[link[u_]];
for (int i = head[u_]; i; i = ne[i]) {
Dfs2(v[i], top_);
sum[u_] += sum[v[i]];
}
}
void Work(std :: string S_) {
std :: vector <int> node;
ll ans = 0;
int n = S_.length(), now = 1;
for (int i = 0; i < n; ++ i) {
now = ch[now][S_[i] - 'a'];
if (only[now] != - 1) node.push_back(top[now]);
}
std :: sort(node.begin(), node.end());
for (int i = 0, n = node.size(); i < n; ++ i) {
if (i != 0) {
if (node[i] == node[i - 1]) continue;
}
ans += sum[node[i]];
}
printf("%lld\n", ans);
}
//=============================================================
int main() {
int T = read();
for (num = 1; num <= T; ++ num) {
std :: cin >> S[num];
int n = S[num].length(), last = 1;
for (int j = 0; j < n; ++ j) last = Insert(S[num][j] - 'a', last);
}
for (int i = 2; i <= node_num; ++ i) AddEdge(link[i], i);
Dfs1(1), Dfs2(1, 0);
for (int i = 1; i <= T; ++ i) Work(S[i]);
return 0;
}

P4081 [USACO17DEC]Standing Out from the Herd P的更多相关文章

  1. P4081 [USACO17DEC]Standing Out from the Herd

    思路 对所有串建立广义SAM,之后记录SZ,统计本质不同子串时只统计SZ=1的即可 代码 #include <cstdio> #include <algorithm> #inc ...

  2. [洛谷P4081][USACO17DEC]Standing Out from the Herd

    题目大意:给你$n$个字符串,对每个字符串求出只在这个字符串中出现的字串的个数 题解:先建广义$SAM$,然后对每个点统计一下它的子树中是不是都是在同一个字符串中的,是的话,就把这个点标成这一个字符串 ...

  3. 后缀自动机再复习 + [USACO17DEC] Standing Out from the Herd

    here:https://oi-wiki.org/string/sam/ 下面转自 KesdiaelKen的雷蒻论坛 来个广义后缀自动机模板题 [USACO17DEC]Standing Out fro ...

  4. [USACO17DEC]Standing Out from the Herd(广义后缀自动机)

    题意 定义一个字符串的「独特值」为只属于该字符串的本质不同的非空子串的个数.如 "amy" 与 “tommy” 两个串,只属于 "amy" 的本质不同的子串为 ...

  5. 【[USACO17DEC]Standing Out from the Herd】

    题目 不会广义\(SAM\)啊 但信仰插入特殊字符就可以搞定一切了 我们先把所有的串搞在一起建出一个\(SAM\),记得在中间插入特殊字符 对于\(parent\)树上的一个节点,只有当其\(endp ...

  6. Luogu4081 USACO17DEC Standing Out from the Herd(广义后缀自动机)

    建出广义SAM,通过parent树对每个节点求出其是否仅被一个子串包含及被哪个包含. 写了无数个sam板子题一点意思都没啊 #include<bits/stdc++.h> using na ...

  7. 【LuoguP4081】[USACO17DEC]Standing Out from the Herd

    题目链接 题意 给定多个字符串,每个串中仅在该串中出现的本质不同的子串个数. Sol 多串匹配想到用广义SAM. 之后从串的匹配角度不是很好做.发现一个本质不同的串最多只会贡献到一个串的答案里. 那么 ...

  8. 【BZOJ5137】Standing Out from the Herd(后缀自动机)

    [BZOJ5137]Standing Out from the Herd(后缀自动机) 题面 BZOJ 洛谷 题解 构建广义后缀自动机 然后对于每个节点处理一下它的集合就好了 不知道为什么,我如果按照 ...

  9. BZOJ5137: [Usaco2017 Dec]Standing Out from the Herd(广义后缀自动机,Parent树)

    Description Just like humans, cows often appreciate feeling they are unique in some way. Since Farme ...

随机推荐

  1. linux 实用指令搜索查找类

    linux 实用指令搜索查找类 目录 linux 实用指令搜索查找类 find指令 locate指令 grep指令和管道符号 | find指令 说明 从指定目录向下递归地遍历其各个子目录,将满足条件的 ...

  2. C语言time函数获取当前时间

    以前放了个链接,但是原作者把博文删了,这里放一个获取时间的代码,已经比较详细所以不做注释 #include<stdio.h> #include<time.h> #include ...

  3. window ntp服务

    一.确定服务端和客户端处于同一网段,能相互 Ping 通. 二.服务器端(Server)配置 1.选择一台服务器(Windows 系统)作为时间同步服务器: 2.Win + R (运行 cmd 命令行 ...

  4. Swift alert 倒计时

    let title: String = "您的开奖时间为" let time: String = "2017-10-23 12:23:18" let count ...

  5. idea maven 项目 遇到 "Module not specified" 解决方法

    1. 原因:我这边出现的原因是 其他同事在提交代码是 将  这个文件夹也提交了,idea 会加载 .idea 里的配置(即 他的配置),而我的 maven 配置不同,导致出错. 2. 解决方法:删除这 ...

  6. Spring Cloud Alibaba微服务架构入门最容易理解篇

    微服务架构介绍 Spring Cloud Alibaba推荐的微服务生态架构基于分层架构实现如下: 接入层:最外层为LVS+Keepalived,可承受几十万级高并发流量洪峰,然后再通过内层的ngin ...

  7. Windows 10 彻底关闭 Antimalware Service Executable 降低内存占用

    概述 最近给内网的一台电脑安装 Windows 10 专业版系统,由于此电脑不会涉及到不安全因素,所以杀毒软件非必须. 以最大限度节省系统资源考虑,默认安装的 Micoroft Defender 占用 ...

  8. 小迪安全 Web安全 基础入门 - 第二天 - Web应用&架构搭建&漏洞&HTTP数据包&代理服务器

    一.网站搭建 1.域名.是由一串用点分隔的字符组成的互联网上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位.域名可以说是一个IP地址的代称,目的是为了便于记忆后者. 2.子域名.在 ...

  9. LuoguP1723 高手过愚人节 题解

    Content 有 \(n\) 次询问,每次询问给定一个字符串 \(s\),求这个字符串最长的回文子串的长度. 数据范围:\(n\) 无解(至少从题面来看是这样的),字符串长度目测应该在 \(10^7 ...

  10. CF938A Word Correction 题解

    Content 有一个长度为 \(n\) 的,只包含小写字母的字符串,只要有两个元音字母相邻,就得删除后一个元音字母(\(\texttt{a,e,i,o,u,y}\) 中的一个),请求出最后得到的字符 ...