题目:

洛谷4770

UOJ395

分析:

一个很好的SAM应用题……

一句话题意:给定一个字符串\(S\)。每次询问给定字符串\(T\)和两个整数\(l\)、\(r\),求\(T\)有多少个本质不同的非空子串不是\(S[l,r]\)的子串。

首先显然是“正难则反”,求有多少个本质不同的非空子串是\(S[l,r]\)的子串(下面的“答案”一词指的是这个值)。先考虑没有\(l\)和\(r\)限制的情况。分别处理询问。对于\(S\)建出后缀自动机。枚举\(T\)的所有前缀,在\(S\)的后缀自动机上走(匹配)。每个前缀对答案的贡献就是这个前缀有多少后缀出现在\(S\)中,即当前在后缀自动机上匹配的深度。

但是直接把所有贡献都加起来是错的,因为没有保证本质不同。于是对\(T\)也建出后缀自动机,不统计每个前缀的贡献,而是统计每个结点的贡献,即统计每个结点对应的本质不同的字符串中有多少个在\(S\)中出现。记\(lim[i]\)为\(T\)的前缀\(i\)有多少个后缀在\(S\)中出现,那么一个结点\(p\)的贡献就是\(max(0,min(lim[i],max[p])-max[fa[p]])\),其中\(i\)是\(p\)的\(Right\)集合中的任意一个点。由于以\(Right\)集合中任意一点结尾的,长度不超过\(max[p]\)的子串都是相同的,所以\(i\)的选取不会影响\(min(lim[i],max[p])\)的值。

至于带\(l\)和\(r\)限制的情况,用线段树合并求出\(S\)的后缀自动机上每个结点的\(Right\)集合。\(T\)在\(S\)的后缀自动机上匹配时,要查询要走到的结点的\(Right\)集合与\([l+len,r]\)的交集是否非空(\(len\)是当前匹配长度,即查询是否有一个长为\(len+1\)的子串在\(S[l,r]\)中出现过)。

代码:

统计答案的时候直接算了这个结点对应的字符串中有多少个不是\(S[l,r]\)的子串,因此式子和上面分析的略有不同。

#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <string>
#define _ 0
using namespace std; namespace zyt
{
const int N = 5e5 + 10, B = 20;
template<typename T>
inline bool read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != EOF && c != '-' && !isdigit(c));
if (c == EOF)
return false;
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
return true;
}
inline void read(string &s)
{
char buf[N];
scanf("%s", buf);
s = buf;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
typedef long long ll;
const int CH = 26;
inline int ctoi(const char c)
{
return c - 'a';
}
namespace Segment_Tree
{
struct node
{
int sum, s[2];
}tree[(N << 1) * B];
int cnt, head[N << 1];
int insert(const int lt, const int rt, const int pos)
{
int rot = ++cnt;
++tree[rot].sum;
if (lt == rt)
return rot;
int mid = (lt + rt) >> 1;
if (pos <= mid)
tree[rot].s[0] = insert(lt, mid, pos);
else
tree[rot].s[1] = insert(mid + 1, rt, pos);
return rot;
}
int merge(const int rot1, const int rot2)
{
if (!rot1)
return rot2;
if (!rot2)
return rot1;
int rot = ++cnt;
int lt = merge(tree[rot1].s[0], tree[rot2].s[0]);
int rt = merge(tree[rot1].s[1], tree[rot2].s[1]);
tree[rot] = (node){tree[rot1].sum + tree[rot2].sum, lt, rt};
return rot;
}
int query(const int rot, const int lt, const int rt, const int ls, const int rs)
{
if (ls > rs)
return 0;
if (ls <= lt && rt <= rs)
return tree[rot].sum;
int mid = (lt + rt) >> 1, ans = 0;
if (ls <= mid)
ans += query(tree[rot].s[0], lt, mid, ls, rs);
if (rs > mid)
ans += query(tree[rot].s[1], mid + 1, rt, ls, rs);
return ans;
}
}
struct SAM
{
int last, cnt;
struct node
{
int max, fa, first, s[CH];
}tree[N << 1];
void init()
{
last = cnt = 1;
memset(tree[1].s, 0, sizeof(int[CH]));
}
void insert(const char c)
{
int x = ctoi(c);
int np = ++cnt, p = last;
memset(tree[np].s, 0, sizeof(int[CH]));
tree[np].max = tree[p].max + 1;
while (p && !tree[p].s[x])
tree[p].s[x] = np, p = tree[p].fa;
if (!p)
tree[np].fa = 1;
else
{
int q = tree[p].s[x];
if (tree[p].max + 1 == tree[q].max)
tree[np].fa = q;
else
{
int nq = ++cnt;
memcpy(tree[nq].s, tree[q].s, sizeof(int[CH]));
tree[nq].first = tree[q].first;
tree[nq].max = tree[p].max + 1;
tree[nq].fa = tree[q].fa;
tree[np].fa = tree[q].fa = nq;
while (p && tree[p].s[x] == q)
tree[p].s[x] = nq, p = tree[p].fa;
}
}
last = np;
}
static int buf[N << 1];
void topo()
{
static int count[N];
int maxx = 0;
memset(count, 0, sizeof(count));
for (int i = 1; i <= cnt; i++)
++count[tree[i].max], maxx = max(maxx, tree[i].max);
for (int i = 1; i <= maxx; i++)
count[i] += count[i - 1];
for (int i = cnt; i > 0; i--)
buf[count[tree[i].max]--] = i;
}
void build(const string &s, const bool segment)
{
using Segment_Tree::head;
init();
for (int i = 0; i < s.size(); i++)
{
insert(s[i]);
tree[last].first = i;
if (segment)
head[last] = Segment_Tree::insert(0, s.size() - 1, i);
}
if (segment)
{
topo();
for (int i = cnt; i > 0; i--)
{
using Segment_Tree::head;
head[tree[buf[i]].fa] = Segment_Tree::merge(head[tree[buf[i]].fa], head[buf[i]]);
}
}
}
}s1, s2;
int SAM::buf[N << 1], lim[N];
string s;
ll solve(const string &t, const int l, const int r)
{
using Segment_Tree::head;
using Segment_Tree::query;
SAM::node *tree = s1.tree;
int now = 1, len = 0;
for (int i = 0; i < t.size(); i++)
{
int x = ctoi(t[i]), nxt = tree[now].s[x];
while (now && !nxt)
now = tree[now].fa, nxt = tree[now].s[x], len = tree[now].max;
while (now)
{
int tmp = 0;
while (len >= 0 && !(tmp = query(head[nxt], 0, s.size() - 1, l + len, r)))
{
--len;
if (len == tree[tree[now].fa].max)
now = tree[now].fa, nxt = tree[now].s[x];
}
if (tmp)
{
now = nxt, ++len;
break;
}
else
now = tree[now].fa, nxt = tree[now].s[x], len = tree[now].max;
}
if (!now)
now = 1, len = 0;
lim[i] = len;
}
ll ans = 0;
for (int i = 1; i <= s2.cnt; i++)
ans += max(0, s2.tree[i].max - max(s2.tree[s2.tree[i].fa].max, lim[s2.tree[i].first]));
return ans;
}
int work()
{
int T;
read(s), read(T);
s1.build(s, true);
while (T--)
{
using namespace Segment_Tree;
static string t;
int l, r;
read(t), read(l), read(r);
--l, --r;
s2.build(t, false);
write(solve(t, l, r)), putchar('\n');
}
return (0^_^0);
}
}
int main()
{
return zyt::work();
}

最新文章

  1. jquery遍历选中checkbox的id
  2. AP是什么
  3. JavaScript的面向对象编程(OOP)(二)——原型
  4. HUST 1010 The Minimum Length(KMP,最短循环节点,即i-Next[i])
  5. Google Maps API 调用实例
  6. ALTIUM 10 过孔设置开窗、不开窗
  7. AFNetworiking与ASIHttpRequest对比
  8. 【ArcGIS 10.2新特性】ArcGIS 10.2 for Server新特性
  9. docfx (一)
  10. lodop打印收费小票
  11. Dynamics CRM The difference between UserId and InitiatingUserId in Plugin
  12. kali权限提升之本地提权
  13. iOS-Mac上进行Fluttrt的安装
  14. zookeeper和kafka的使用
  15. 大话DI依赖注入+IOC控制反转(二) 之 浅析.Net Core中的DI与IOC
  16. jenkins使用Publish Over SSH中遇到的问题
  17. 005-spring cache-配置缓存存储
  18. 如何建立ElasticSearch里的mappings?
  19. shiro之cache问题
  20. 课时48.表单标签-H5(了解)

热门文章

  1. 一:安装centos 7最小编程环境 xfce桌面
  2. software collection
  3. PAT 1020. Tree Traversals
  4. 54. spring boot日志升级篇—logback【从零开始学Spring Boot】
  5. JS动态添加div,然后在div中添加元素
  6. Ubuntu 16.04下使用gcc输出汇编的.0文件为可执行文件时出现:`_start&#39;被多次定义
  7. 25、Java并发性和多线程-阻塞队列
  8. JFrame实现批量获取Android安装包安全证书MD5
  9. 怎样编译和安装memcached
  10. poj 1331 Multiply