P4081 [USACO17DEC]Standing Out from the Herd P
知识点: 广义 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\) 的「独特值」。
上述算法瓶颈是枚举子串。发现前缀有一些好性质:
- 连续前缀 对应状态在 SAM 上也是连续的,实现时直接把串扔到 SAM 上跑 即得对应状态。
- 前缀对应状态到 parent 树根的链上 包含该前缀所有后缀,可以包含所有子串信息。
考虑仅枚举前缀,枚举复杂度变为线性。
发现一些结论:
- parent 树上一条从叶到根的链,维护的串的个数,是单调不减的。
- 若某状态 \(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的更多相关文章
- P4081 [USACO17DEC]Standing Out from the Herd
思路 对所有串建立广义SAM,之后记录SZ,统计本质不同子串时只统计SZ=1的即可 代码 #include <cstdio> #include <algorithm> #inc ...
- [洛谷P4081][USACO17DEC]Standing Out from the Herd
题目大意:给你$n$个字符串,对每个字符串求出只在这个字符串中出现的字串的个数 题解:先建广义$SAM$,然后对每个点统计一下它的子树中是不是都是在同一个字符串中的,是的话,就把这个点标成这一个字符串 ...
- 后缀自动机再复习 + [USACO17DEC] Standing Out from the Herd
here:https://oi-wiki.org/string/sam/ 下面转自 KesdiaelKen的雷蒻论坛 来个广义后缀自动机模板题 [USACO17DEC]Standing Out fro ...
- [USACO17DEC]Standing Out from the Herd(广义后缀自动机)
题意 定义一个字符串的「独特值」为只属于该字符串的本质不同的非空子串的个数.如 "amy" 与 “tommy” 两个串,只属于 "amy" 的本质不同的子串为 ...
- 【[USACO17DEC]Standing Out from the Herd】
题目 不会广义\(SAM\)啊 但信仰插入特殊字符就可以搞定一切了 我们先把所有的串搞在一起建出一个\(SAM\),记得在中间插入特殊字符 对于\(parent\)树上的一个节点,只有当其\(endp ...
- Luogu4081 USACO17DEC Standing Out from the Herd(广义后缀自动机)
建出广义SAM,通过parent树对每个节点求出其是否仅被一个子串包含及被哪个包含. 写了无数个sam板子题一点意思都没啊 #include<bits/stdc++.h> using na ...
- 【LuoguP4081】[USACO17DEC]Standing Out from the Herd
题目链接 题意 给定多个字符串,每个串中仅在该串中出现的本质不同的子串个数. Sol 多串匹配想到用广义SAM. 之后从串的匹配角度不是很好做.发现一个本质不同的串最多只会贡献到一个串的答案里. 那么 ...
- 【BZOJ5137】Standing Out from the Herd(后缀自动机)
[BZOJ5137]Standing Out from the Herd(后缀自动机) 题面 BZOJ 洛谷 题解 构建广义后缀自动机 然后对于每个节点处理一下它的集合就好了 不知道为什么,我如果按照 ...
- 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 ...
随机推荐
- CAD简介
Computer-aided design (CAD) is the use of computers (or workstations) to aid in the creation, modifi ...
- 关于浏览器,从输入URL到呈现页面过程!(主讲TCP/IP协议)
一.文本对话--从请求到响应 我们在浏览器中输入一个 URL,回车之后便会在浏览器中观察到页面内容.实际上这个过程是: (1)浏览器向网站所在的服务器发送了一个 Request(请求) (2)网站服务 ...
- linux下的C++多线程
原文链接:http://blog.csdn.net/lee1054908698/article/details/54633056 本随笔作为多线程笔记使用,内容完全照搬原博 多线程是多任务处理的一种特 ...
- ORACLE 查询sql和存储性能思路
1.确定session id 如果是存储过程,在程序开头设置客户端标识.并根据标识获取session id. DBMS_SESSION.set_identifier('XXX'); select * ...
- Default arguments and virtual function
Predict the output of following C++ program. 1 #include <iostream> 2 using namespace std; 3 4 ...
- zookeeper 异常 :stat is not executed because it is not in the whitelist. Connection closed b
1 .问题 1.启动 zookeeper 后 用指令: telnet 127.0.0.1 2181 连接 提示输入指令 :stat 后报错,然后关闭连接 2.问题解决: 修改启动指令 zkServe ...
- 小程序的事件 bindtap bindinput
一.bindtap事件 在wxml文件里绑定: <view class='wel-list' bindtap='TZdown'> <image src="/images/w ...
- 【力扣】146. LRU缓存机制
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制.它应该支持以下操作: 获取数据 get 和 写入数据 put . 获取数据 get(key) - 如果关键字 (key) ...
- Notepad++ 常用功能:批量取消替换换行、强制刷新数据
批量取消替换换行 换行批量替换成空格 Ctrl+F 打开查找替换窗口,使用:\r\n 替换成 空格,全部替换 强制刷新数据源,重新加载数据 快捷键:Ctrl+R 或者 Alt+F 然后 L 或者点击菜 ...
- Spring核心原理分析之MVC九大组件(1)
本文节选自<Spring 5核心原理> 1 什么是Spring MVC Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 S ...