首页 > 其他分享 >Solution - Atcoder ABC280Ex Substring Sort

Solution - Atcoder ABC280Ex Substring Sort

时间:2024-07-29 08:58:11浏览次数:17  
标签:Sort Atcoder int len Substring maxn fail now operatorname

对于这种子串问题,且有多个基础串,一个比较直观的想法就是先上个广义 SAM。

考虑 SAM 与字典序如何联系上。
因为跳 \(\operatorname{fail}\) 相当于是删除子串的一个前缀,直接这样子明显是不行的,因为跳了 \(\operatorname{fail}\) 字典序没有一个很直观地表示。
但是反过来考虑反串,那么在反串上跳 \(\operatorname{fail}\) 就相当于是删除后缀了。
发现这似乎就能联系上了。

下面一部分指的字符串就是反串中对应的字符串。

建出来 \(\operatorname{fail}\) 树,节点 \(u\) 的儿子 \(v\) 按照 \(s_{v, \operatorname{len}(u) + 1}\) 排序即可(\(s_v\) 指 \(v\) 节点对应的字符串),就相当于是确定好了 \(u\) 的儿子字典序大小的相对顺序。
这里的正确性是考虑不会存在 \(u\) 的儿子 \(a, b\) 满足 \(s_{a, \operatorname{len}(u) + 1} = s_{b, \operatorname{len}(v) + 1}\)。

那么对于 \(\operatorname{fail}\) 树跑出来的 \(\operatorname{dfs}\) 序就会是对应的字典序的顺序了。

对于询问就比较简单了。

对于节点 \(u\),这里对应的字符串数量有 \(\operatorname{cnt}_u\times (\operatorname{len}(u) - \operatorname{len}(\operatorname{fail}_u))\)。
然后按照 \(\operatorname{dfs}\) 序做前缀和 \(\operatorname{sum}_i\)。

那么对于询问 \(x\)。
找到最靠前的 \(i\) 使得 \(x\le \operatorname{sum}_i\)。
同时如果知道了 \(i\) 节点对应 SAM 的节点 \(p\),就能知道字符串的长度为 \(\operatorname{len}(\operatorname{fail}_u) + 1 + \lfloor\frac{x - \operatorname{sum}_{i - 1} - 1}{\operatorname{cnt}_p}\rfloor\),对于在哪个子串和右端点只需要初始建 Trie 时预处理,同时 \(\operatorname{fail}\) 树上 dfs 时对应处理好即可。

因为题目满足 \(x_j\) 递增,那么对应的 \(i\) 也是递增的,就不需要二分了。

时间复杂度 \(\mathcal{O}((\sum\limits_i|s_i|)|\Sigma|\log |\Sigma| + q)\)。

#include<bits/stdc++.h>
using ll = long long;
#define fi first
#define se second
const int maxn = 2e5 + 10;
int tr[maxn][26], m = 1;
std::pair<int, int> idw[maxn]; int siz[maxn];
std::string s[maxn];
int id[maxn], H[maxn], ed[maxn], cnt[maxn], len[maxn], ch[maxn][26], fail[maxn], tot = 1;
std::vector<int> son[maxn];
inline int append(int p, int c, int cnt_, int H_, int ed_) {
   int now = ++tot;
   len[now] = len[p] + 1, cnt[now] = cnt_, H[now] = H_, ed[now] = ed_;
   while (p && ! ch[p][c]) ch[p][c] = now, p = fail[p];
   if (! p) fail[now] = 1;
   else {
      int q = ch[p][c];
      if (len[p] + 1 == len[q]) fail[now] = q;
      else {
         int nq = ++tot;
         memcpy(ch[nq], ch[q], sizeof(ch[nq])), fail[nq] = fail[q], len[nq] = len[p] + 1;
         fail[q] = fail[now] = nq;
         while (p && ch[p][c] == q) ch[p][c] = nq, p = fail[p];
      }
   }
   return now;
}
void dfs1(int u) {
   for (int v : son[u])
      dfs1(v), cnt[u] += cnt[v], H[u] = H[v], ed[u] = ed[v];
}
ll sum[maxn]; int K, ids[maxn];
void dfs2(int u) {
   ids[++K] = u;
   sum[K] = sum[K - 1] + 1ll * cnt[u] * (len[u] - len[fail[u]]);
   for (int v : son[u])
      dfs2(v);
}
int main() {
   std::ios::sync_with_stdio(false);
   std::cin.tie(0), std::cout.tie(0);
   int n; std::cin >> n;
   for (int i = 1; i <= n; i++) {
      std::cin >> s[i];
      std::reverse(s[i].begin(), s[i].end());
      for (int u = 1, j = 0; j < s[i].size(); j++) {
         int &v = tr[u][s[i][j] - 'a'];
         if (! v) v = ++m;
         u = v, idw[u] = {i, j}, siz[u]++;
      }
   }
   id[1] = 1;
   for (int i = 1; i <= m; i++)
      for (int c = 0; c < 26; c++)
         if (tr[i][c])
            id[tr[i][c]] = append(id[i], c, siz[tr[i][c]], idw[tr[i][c]].fi, idw[tr[i][c]].se);
   for (int i = 2; i <= tot; i++)
      son[fail[i]].push_back(i);
   dfs1(1);
   for (int i = 1; i <= tot; i++)
      std::sort(son[i].begin(), son[i].end(), [&](int x, int y) {
         return s[H[x]][ed[x] - len[i]] < s[H[y]][ed[y] - len[i]];
      });
   dfs2(1);
   int Q; std::cin >> Q;
   for (int w = 1; Q--; ) {
      ll x; std::cin >> x;
      while (sum[w] < x) w++;
      int p = ids[w], l = len[fail[p]] + 1 + (x - sum[w - 1] - 1) / cnt[p];
      std::cout << H[p] << ' ' << (s[H[p]].size() - ed[p]) << ' ' << (s[H[p]].size() - ed[p] + l - 1) << '\n';
   }
   return 0;
}

标签:Sort,Atcoder,int,len,Substring,maxn,fail,now,operatorname
From: https://www.cnblogs.com/rizynvu/p/18329294

相关文章

  • Solution - Atcoder ARC114F Permutation Division
    令\(a\)为题目中的\(P\)。首先考虑后手的重排策略是什么。因为\(a\)是个排列,元素互不相同,那么肯定就是按照每一段的第一个数的大小,越大的放在越前面。那么当\(a_1\lek\)的时候,显然先手会把以\(1\simk\)开头来划分段。因为否则存在一个开头\(>k\)的段,后手把其放......
  • AtCoder Beginner Contest 363 题解 A-D(待补充)
    A-PilingUp1.1思路其实就是向上取百位的整,需要增加多少,123则为200-123=177;1.2代码voidsolve(){intn;cin>>n;intt=n/100;cout<<(t+1)*100-n;}B-JapaneseCursedDoll 2.1思路就是判断最少需要多少天,会有大于等于P个人......
  • AtCoder Beginner Contest 363
    比赛地址添加链接描述A-PilingUp算法:模拟题目大意在AtCoder竞赛平台中,用户的等级通过正整数分数表示,并根据分数显示相应数量的^符号。分数与^符号显示的规则如下:当分数在1到99(包括99)之间时,显示一个^。当分数在100到199(包括199)之间时,显示两个^。......
  • AtCoder Beginner Contest 364 补题记录(A~F)
    VP五十八分钟苏童流体。好耶A#defineGLIBCXX_DEBUG#include<iostream>#include<cstring>#include<cstdio>#defineintlonglongconstintN=500100;std::strings[N];signedmain(){intn,cnt=1;scanf("%lld",&n);f......
  • 14 Python列表操作内置函数(append、+、extend、insert、index、del、pop、remove、len
     欢迎来到@一夜看尽长安花博客,您的点赞和收藏是我持续发文的动力对于文章中出现的任何错误请大家批评指出,一定及时修改。有任何想要讨论的问题可联系我:[email protected]。发布文章的风格因专栏而异,均自成体系,不足之处请大家指正。   专栏:java全栈C&C++PythonAIP......
  • AtCoder Beginner Contest 364
    A-GluttonTakahashi(abc364A)题目大意给定\(n\)个字符串,问是否有两个相邻的sweet。解题思路遍历判断当前字符串与上一个字符串是否都为sweet即可。神奇的代码#include<bits/stdc++.h>usingnamespacestd;usingLL=longlong;intmain(void){ios::sync_......
  • AtCoder Beginner Contest 364 复盘
    AtCoderBeginnerContest364当你发现你的做法假了时,再看看题目的时限和空限,你就有可能发现,你的做法真了。本场口胡出了\(5\)题的正解,但是只写出了\(3\)题。弱弱又智智。A-GluttonTakahashi&B-GridWalk&C-MinimumGlutton签到D-K-thNearest算口胡出半......
  • AtCoder Beginner Contest 362
    题目链接:AtCoderBeginnerContest362A.BuyaPentag:签到B.RightTriangletag:模拟C.RightTriangletag:贪心Description:给定\(n\)对整数\(l_i,r_i\);求一个整数序列,满足\(l_i<=x_i<=r_i\),且\(\sum_{i}^{n}x_i=0\);如果没有则输出\(-1\)。Solution:先判断是否有解,令......
  • AtCoder Beginner Contest 363
    上周去玩了(逃A-PilingUp(abc363A)题目大意给定分数,问晋级还差多少分。分别到\(100,200,300\)分能晋级。解题思路找到第一个大于当前分数的,其差即为答案。神奇的代码#include<bits/stdc++.h>usingnamespacestd;usingLL=longlong;intmain(void){io......
  • Arrays.sort()与Collections.sort()的用法以及区别
    目录Arrays.sort()与Collections.sort()的区别对象数组的排序方式Arrays.sort()的方法1.Arrays.sort(int[]a)2.Arrays.sort(int[]a,intfromIndex,inttoIndex)3.Arrays.sort(Integer[]a,Comparatorcmp)Collections.sort()的方法1.sort(Listlist)2.sort(Listlist......