「TJOI2015」弦论

知识点: SAM

原题面 LojLuogu


而莲子则是超统一物理学。最近在做弦论方面的研究,还顺利吗?
———— 《梦违科学世纪》永夜の報い ~ Imperishable Night


题意简述

给定长度为 \(n\) 的字符串 和参数 \(t\)
\(t=0\),求它的第 \(k\) 小本质不同子串。
\(t=1\),求它的第 \(k\) 小子串。
\(1\le n\le 5\times 10^5,\ t<2,\ k\le 10^9\)


分析题意

分享一下做题经历:

算法一

先考虑 \(T=1\)
建 SAM,dfs 求每个状态的出现次数 \(size\)
再在 DAWG 上按照字典序跑,跑到一个节点就令 \(k- size_i\),并转移。
\(k=0\) 时,当前跑到的字符串即为所求。

\(T=0\) 时,直接赋 \(size_i = 1\),即每个状态仅出现 \(1\) 次。
再按上述过程跑即可。


算法二

然后 T 了。
发现这玩意复杂度上限是 \(O(n+k)\) 的,\(T=0\) 时必定达到上限。
跑到一个节点才令 \(k - size_i\),并转移 太慢啦!

考虑权值线段树维护第 k 小的过程:
若当前跑出串 \(S\),考虑下一步转移,向 \(c\) 转移,相当于遍历所有前缀为 \(S+c\) 的串。
考虑向 \(a\) 转移,若所有前缀为 \(S+a\) 的串的数量 \(<k\),答案串一定不以 \(S+a\) 作为前缀。
遍历它们只会浪费时间,仅需使 \(k-\) 所有前缀为 \(S+a\) 的串的数量,再考虑向 \(b\) 转移。

考虑维护以某字符串作为前缀的,所有子串的数量。
这玩意咋维护啊?? 自己 YY 了一波:

考虑 parent树的性质,对于一个状态,其在 parnet 树上的子孙,均以其为 后缀
统计某状态为 后缀 的子串的数量,可以直接在 parent 树上统计。

现在要求统计作为 前缀 的情况,想到建反串的 parent 树,dfs 即可求出以某状态为 前缀 时的子串数量。
在正串的 DAWG 上跑答案时,在转移到某状态前根据它判断即可。


但这玩意复杂度是假的/fad,还是 T 了。
因为不知道反串,正串 SAM 状态的映射关系。
想查询以某状态为 前缀 时的子串数量,只能在反串的 SAM 中把该状态的反串跑出来,复杂度就爆炸了。
这个 idea 可能还不错?以后说不定会用到。


算法三

再 YY 一波:
考虑在 SAM 上暴跳时的搜索树。
发现有许多重复节点,它们的子树还会被跑多次,考虑记忆化子树的 size。
没有去实现,正确性未知。


算法四

一个子串唯一对应 SAM 中的一条路径,第 \(k\) 小子串即第 \(k\) 小路径。
预处理每个状态的路径条数,查询类似权值线段树。

注意预处理路径条数时按照拓扑序 DP,
\(u\) 可转移到的节点更新 \(u\)
需要先将各状态按照 \(len\) 进行排序,为保证复杂度使用了计数排序。

总复杂度 \(O(|S| + |ans|\cdot |\sum|)\)


代码实现

算法一

//
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 5e5 + 10;
const int kMaxm = 26;
//=============================================================
int n, m, t, k, last = 1, node_num = 1;
int ch[kMaxn << 1][kMaxm], link[kMaxn << 1], len[kMaxn << 1], size[kMaxn << 1];
int edge_num, head[kMaxn << 1], v[kMaxn << 1], ne[kMaxn << 1];
char S[kMaxn], ans[kMaxn];
bool flag;
//=============================================================
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 GetMax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
void Insert(int c_) {
  int p = last, now = last = ++ node_num;
  size[now] = 1, len[now] = len[p] + 1;
  for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
  if (! p) {link[now] = 1; return ;}
  int q = ch[p][c_];
  if (len[q] == len[p] + 1) {link[now] = q; return ;}
  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;
}
void AddEdge(int u_, int v_) {
  v[++ edge_num] = v_, ne[edge_num] = head[u_], head[u_] = edge_num;
}
void Dfs(int u_) {
  for (int i = head[u_]; i; i = ne[i]) {
    Dfs(v[i]);
    size[u_] += size[v[i]];
  }
}
void Query(int u_, int lth) {
  if (u_ != 1){
    if (size[u_] >= k) {
      printf("%s", ans + 1);
      flag = true;
      return ;
    }
    k -= size[u_]; 
  }
  for (int c = 0; c < kMaxm; ++ c) {
    if (flag) return ;
    if (ch[u_][c]) {
      ans[lth] = c + 'a';
      Query(ch[u_][c], lth + 1);
      ans[lth] = 0;
    }
  }
}
//=============================================================
int main() {
  scanf("%s", S + 1);
  n = strlen(S + 1);
  t = read(), k = read();
  for (int i = 1; i <= n; ++ i) Insert(S[i] - 'a');
  if (t) {
    for (int i = 2; i <= node_num; ++ i) AddEdge(link[i], i);
    Dfs(1);
  } else {
    for (int i = 2; i <= node_num; ++ i) size[i] = 1;
  }
  Query(1, 1);
  if (! flag) printf("-1");
  return 0;
}

算法二

//
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 1e6 + 10;
const int kMaxm = 26;
//=============================================================
int n, m, t, k, last = 1, node_num = 1, node_num1;
int ch[kMaxn << 1][kMaxm], link[kMaxn << 1], len[kMaxn << 1];
int edge_num, head[kMaxn << 1], v[kMaxn << 1], ne[kMaxn << 1];
int size[kMaxn << 1], sum[kMaxn << 1];
char S[kMaxn >> 1], ans[kMaxn >> 1];
bool flag;
//=============================================================
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 GetMax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
void Insert(int c_, int start) {
  int p = last, now = last = ++ node_num;
  size[now] = 1, len[now] = len[p] + 1;
  for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
  if (! p) {link[now] = start; return ;}
  int q = ch[p][c_];
  if (len[q] == len[p] + 1) {link[now] = q; return ;}
  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;
}
void AddEdge(int u_, int v_) {
  v[++ edge_num] = v_, ne[edge_num] = head[u_], head[u_] = edge_num;
}
void Dfs(int u_) {
  for (int i = head[u_]; i; i = ne[i]) {
    Dfs(v[i]);
    if (! t) {
      size[u_] = 1; 
    } else {
      size[u_] += size[v[i]]; 
    }
    sum[u_] += sum[v[i]];
  }
  sum[u_] += size[u_] * (len[u_] - len[link[u_]]);
}
int QuerySum(int u_, int lth_) {
  if (! lth_) return u_;
  return QuerySum(ch[u_][ans[lth_] - 'a'], lth_ - 1);
}
void Query(int u_, int lth_) {
  if (u_ != 1){
    int ret = QuerySum(node_num1 + 1, lth_ - 1);
    if (sum[ret] < k) {
      k -= sum[ret];
      return ; 
    }
    if (size[u_] >= k) {
      printf("%s", ans + 1);
      flag = true;
      return ;
    }
    k -= size[u_]; 
  }
  for (int c = 0; c < kMaxm; ++ c) {
    if (flag) return ;
    if (ch[u_][c]) {
      ans[lth_] = c + 'a';
      Query(ch[u_][c], lth_ + 1);
      ans[lth_] = 0;
    }
  }
}
//=============================================================
int main() {
  scanf("%s", S + 1);
  n = strlen(S + 1);
  t = read(), k = read();
  for (int i = 1; i <= n; ++ i) Insert(S[i] - 'a', 1);
  node_num1 = node_num;
  last = ++ node_num;
  for (int i = n; i >= 1; -- i) Insert(S[i] - 'a', node_num1 + 1);
  for (int i = 2; i <= node_num1; ++ i) AddEdge(link[i], i);
  for (int i = node_num1 + 2; i <= node_num; ++ i) AddEdge(link[i], i);
  Dfs(1); Dfs(node_num1 + 1);
  Query(1, 1);
  if (! flag) printf("-1");
  return 0;
}

算法四

正解

//知识点:SAM
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 1e6 + 10;
const int kMaxm = 26;
//=============================================================
int n, m, t, k, last = 1, node_num = 1;
int ch[kMaxn][kMaxm], link[kMaxn], len[kMaxn], size[kMaxn], sum[kMaxn];
int cnt[kMaxn], id[kMaxn];
char S[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 GetMax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
void Insert(int c_) {
  int p = last, now = last = ++ node_num;
  size[now] = 1, len[now] = len[p] + 1;
  for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
  if (! p) {link[now] = 1; return ;}
  int q = ch[p][c_];
  if (len[q] == len[p] + 1) {link[now] = q; return ;}
  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;
}
void Prepare() {
  for (int i = 1; i <= node_num; ++ i) cnt[len[i]] ++;
  for (int i = 1; i <= node_num; ++ i) cnt[i] += cnt[i - 1];
  for (int i = 1; i <= node_num; ++ i) id[cnt[len[i]] --] = i;
  for (int i = node_num; i; -- i) {
    if (t) {
      size[link[id[i]]] += size[id[i]];
    } else {
      size[id[i]] = 1;
    }
  }
  size[1] = 0;
  for (int i = node_num; i; -- i) {
    sum[id[i]] = size[id[i]];
    for (int j = 0; j < kMaxm; ++ j) {
      if (ch[id[i]][j]) sum[id[i]] += sum[ch[id[i]][j]];
    }
  }
}
void Query() {
  if (k > sum[1]) {
    printf("-1");
    return ;
  }
  for (int now = 1; k > 0; k -= size[now]) {
    int p = 0;
    for (; k > sum[ch[now][p]]; ++ p) k -= sum[ch[now][p]];
    printf("%c", 'a' + p);
    now = ch[now][p];
  }
}
//=============================================================
int main() {
  scanf("%s", S + 1);
  n = strlen(S + 1);
  t = read(), k = read();
  for (int i = 1; i <= n; ++ i) Insert(S[i] - 'a');
  Prepare();
  Query();
  return 0;
}
posted @ 2020-08-17 08:05  Luckyblock  阅读(225)  评论(2编辑  收藏  举报