知识点: 广义 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. day33 前端之css

    day33 前端之css css简介 CSS(Cascading Style Sheet,层叠样式表)定义如何显示HTML元素. # 语法结构 选择器 { 属性名1,属性值 属性名2,属性值 } # ...

  2. MySQL自我保护参数

    上文(MySQL自我保护工具--pt-kill )提到用pt-kill工具来kill相关的会话,来达到保护数据库的目的,本文再通过修改数据库参数的方式达到阻断长时间运行的SQL的目的. 1.参数介绍 ...

  3. 在 windows 系统上 安装与配置 PHP + Apache

    参考:http://www.cnblogs.com/pharen/archive/2012/02/06/2340628.html 在大学时候上过一门PHP课时,因为课堂需要配置过一次PHP+Mysql ...

  4. C++异常处理(try、catch、throw)

    本文为转载 博主原文连接 我们通常希望自己编写的程序能够在异常的情况下也能作出相应的处理,而不至于程序莫名其妙地中断或者中止运行了.在设计程序时应充分考虑各种异常情况,并加以处理. 在C++中,一个函 ...

  5. mybatis-插件开发

    在Executor.StatementHandler.parameterHandler.resultSetHandler创建的时候都有一步这样的操作xxxHandler=interceptorChai ...

  6. js 长按鼠标左键实现溢出内容左右滚动滚动

    var nextPress, prevPress; // 鼠标按下执行定时器,每0.1秒向左移一个li内容的宽度 function nextDown() { nextPress = setInterv ...

  7. MyEclipse配置Spring框架(基础篇)

    一.新建项目,添加spring的相关jar包等 二.创建相关类以及属性和方法 Student.java package com.yh; public class Student implements ...

  8. Mysql报错合集

    目录 一.链接报错 客户端连接mysql出错 链接客户端出错 交互登陆mysql出现warning警告Using a password 导入数据到数据库报错ERROR 1050 登陆数据库提示-bas ...

  9. Apache设置虚拟机端口

    Apache虚拟机设置端口,以45184端口为例httpd-vhosts.conf文件NameVirtualHost *:45184<VirtualHost *:45184>    Doc ...

  10. linux下编译php扩展

    1 在pecl.php.net搜索你需要的php扩展 2 在解压后的扩展目录运行phpize 3 执行编译./configure --with-php-config=/usr/local/php/bi ...