BZOJ 3277 串 (广义后缀自动机)

3277: 串
Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 309  Solved: 118
[Submit][Status][Discuss]
Description
字符串是oi界常考的问题。现在给定你n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串(注意包括本身)。
Input
第一行两个整数n,k。
接下来n行每行一个字符串。

Output
输出一行n个整数,第i个整数表示第i个字符串的答案。

Sample Input
3 1
abc
a
ab

Sample Output
6 1 3

HINT
对于100%的数据,n,k,l<=100000

算法讨论:

首先对这些串建立出广义后缀自动机,同时在建立的时候要保存当前结点都是哪些串的子串,然后建立出Parent树,

对树进行一遍DFS,把一个点的所以后代结点的颜色信息全部合并到自己身上,并用一个数组来维护当前结点有多少颜色,也就是多少个串的子串。

因为我们知道,一个点在Parent树上的父亲结点是其的最长后缀,所以如果一个点有颜色Q,那么其所有祖先结点全部有颜色Q。

然后对于每个串跑自动机,如果一个当前结点的颜色数目小于K,就沿其fail指针向上跳,跳到一个大于等于K的地方,

此时答案应该加上min(这个点的len, 当前ln + 1的最小值)。 至于这个ln是做什么的,容我再想想。

代码:

#include <cstdlib>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <set>
#include <string>

using namespace std;
const int N = 100000 + 5;
const int C = 26;
typedef long long ll;

int n, cnt, k;
int head[N << 1], color[N << 1];
string s[N];
set <int> occ[N << 1];
set <int> :: iterator it;

struct State {
  int pre, len, next[C];
}st[N << 1];

struct SuffixAutomaton {
  int sz, last;

  void Init() {
    sz = last = 1;
    st[sz].pre = -1; st[sz].len = 0;
    sz ++;
  }

  void add(int c, int ccc) {
    int cur = sz ++, p;
    st[cur].len = st[last].len + 1;
    for(p = last; p != -1 && !st[p].next[c]; p = st[p].pre)
      st[p].next[c] = cur;
    if(p == -1) st[cur].pre = 1;
    else {
      int q = st[p].next[c];
      if(st[q].len == st[p].len + 1) st[cur].pre = q;
      else {
        int cle = sz ++;
        st[cle].pre = st[q].pre;
        st[cle].len = st[p].len + 1;
        for(int i = 0; i < C; ++ i) st[cle].next[i] = st[q].next[i];
        for(; p != -1 && st[p].next[c] == q; p = st[p].pre)
          st[p].next[c] = cle;
        st[q].pre = st[cur].pre = cle;
      }
    }
    last = cur;
    occ[cur].insert(ccc);
  }
}sam;

struct Edge {
  int from, to, next;
}edges[N << 1];

void insert(int from, int to) {
  ++ cnt;
  edges[cnt].from = from; edges[cnt].to = to;
  edges[cnt].next = head[from]; head[from] = cnt;
}

void dfs(int u) {
  for(int i = head[u]; i; i = edges[i].next) {
    int v = edges[i].to;
    dfs(v);
    if(occ[u].size() < occ[v].size())
      swap(occ[u], occ[v]);
    for(it = occ[v].begin(); it != occ[v].end(); ++ it)
      occ[u].insert(*it);
  }
  color[u] = occ[u].size();
}

int main() {
  //freopen("stringa.in", "r", stdin);
  //freopen("stringa.out", "w", stdout);

  int __size__ = 50 << 20;
  char *__p__ = (char*)malloc (__size__) + __size__;
  __asm__("movl %0, %%esp" :: "r"(__p__));

  //ios :: sync_with_stdio(false);
  cin >> n >> k;
  sam.Init();
  for(int i = 1; i <= n; ++ i) {
    cin >> s[i];
    int len = s[i].length();
    for(int j = 0; j < len; ++ j) sam.add(s[i][j] - ‘a‘, i);
    sam.last = 1;
  }
  for(int i = 1; i < sam.sz; ++ i)
    if(st[i].pre != -1) insert(st[i].pre, i);
  dfs(1);
  for(int i = 1; i <= n; ++ i) {
    if(k > n) { cout << 0 << " "; continue; }
    ll ans = 0;
    int p = 1, ln = 0, len;
    len = s[i].length();
    for(int j = 0; j < len; ++ j) {
      p = st[p].next[(int) s[i][j] - ‘a‘];
      while(color[p] < k) p = st[p].pre;
      ln = min(ln + 1, st[p].len);
      ans += ln;
    }
    cout << ans << " ";
  }

  //fclose(stdin); fclose(stdout);
  return 0;
}
时间: 2024-10-06 04:36:51

BZOJ 3277 串 (广义后缀自动机)的相关文章

[BZOJ3277]串 广义后缀自动机

3277: 串 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 811  Solved: 329[Submit][Status][Discuss] Description 字符串是oi界常考的问题.现在给定你n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中 至少k个字符串的子串(注意包括本身). Input 第一行两个整数n,k. 接下来n行每行一个字符串. n,k,l<=100000 Output 输出一行n个整数,第i个整数

bzoj 4566 找相同字符 —— 广义后缀自动机

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4566 建出两个串的广义后缀自动机: 统计每个点在两个串中出现次数的子树和,其实就是在两个串中的 right 集合大小: 然后统计答案乘起来即可. 代码如下: #include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; int co

BZOJ 3926: [Zjoi2015]诸神眷顾的幻想乡 广义后缀自动机 后缀自动机 字符串

https://www.lydsy.com/JudgeOnline/problem.php?id=3926 广义后缀自动机是一种可以处理好多字符串的一种数据结构(不像后缀自动机只有处理一到两种的时候比较方便). 后缀自动机可以说是一种存子串的缩小点数的trie树,广义后缀自动机就是更改了一下塞点的方式让它可以塞多个子串. 1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<

【BZOJ3926】[Zjoi2015]诸神眷顾的幻想乡 广义后缀自动机

[BZOJ3926][Zjoi2015]诸神眷顾的幻想乡 Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看.幽香当然也非常高兴啦. 这时幽香发现了一件非常有趣的事情,太阳花田有n块空地.在过去,幽香为了方便,在这n块空地之间修建了n-1条边将它们连通起来.也就是说,这n块空地形成了一个树的结构. 有n个粉丝们来到了太阳花田上.为了表达对幽香生日的祝

BZOJ 2806 [Ctsc2012]Cheat ——后缀自动机 单调队列优化DP

先建出广义后缀自动机. 然后跑出文章中每一个位置的最大匹配距离. 然后定义$f[i]$表示匹配到以$i$结尾的串时,最长的匹配距离. 显然可以二分$L$的取值. 然后容易得到$DP$方程 $f[i]=max(f[i-1],f[j]+i-j)(j<=i-L)$ 然后就发现$j$属于一个区间,然后就可以单调队列优化了. #include <map> #include <ctime> #include <cmath> #include <queue> #in

E. Three strings 广义后缀自动机

http://codeforces.com/problemset/problem/452/E 多个主串的模型. 建立一个广义后缀自动机,可以dp出每个状态的endpos集合大小.同时也维护一个R[]表示那个串出现过. 所以可以算出每个状态的dp[i][k]表示第k个串在第i个状态中出现的次数. 可以知道sigma dp[i][0...k]是等于  endpos集合的大小. 然后把这个贡献加到min(i)....max(i)中去就可以了 差分一下. #include <bits/stdc++.h>

广义后缀自动机

1).自动机的介绍 首先我们先来介绍一下什么是自动机,有限状态自动机的功能是识别字符串,令一个自动机A,若他能识别字符串S,就记为A(S)=Ture,否则A(S)=False. 自动机由五个部分组成,alpha:字符集,state:状态集合,init:初始状态,end:结束状态集合,trans:状态转移函数. 令trans(s,ch)表示当前状态是s,在读入字符ch之后,所到达的状态.如果trans(s,ch)这个转移不存在,为了方便,设其为null,同时null只能转移到null.null表示

hdu 5853 Jong Hyok and String(广义后缀自动机)

题目链接:hdu 5853 Jong Hyok and String 题意: 给你n个字符串,m个询问,每次询问一个字符串 定义set(s)={(i,j)} 表示 s在第i个字符串中出现,且末尾位置为j. 对于一个询问,求set(Qi)=set(t) ,t串的数量. 题解: 如果是n=1,那么就是后缀自动机的一道裸题,答案就是Qi串匹配的最后一个节点x,ml[x]-ml[f[x]]. 现在是多个串,那么就建立一个广义后缀自动机.每次插入一个串后,将last=root,然后继续插下一个就行了. 最

【codeforces666E】Forensic Examination 广义后缀自动机+树上倍增+线段树合并

题目描述 给出 $S$ 串和 $m$ 个 $T_i$ 串,$q$ 次询问,每次询问给出 $l$ .$r$ .$x$ .$y$ ,求 $S_{x...y}$ 在 $T_l,T_{l+1},...,T_r$ 中的哪一个里出现次数最多,输出出现次数最多的串编号(如果有多个则输出编号最小的)以及相应出现次数. $|S|,q\le 5\times 10^5$ ,$\sum\limits_{i=1}^m|T_i|\le 5\times 10^4$ . 题解 广义后缀自动机+树上倍增+线段树合并 对 $S$