首页 > 其他分享 >题解 UOJ577【[ULR #1] 打击复读】

题解 UOJ577【[ULR #1] 打击复读】

时间:2024-04-23 22:47:00浏览次数:39  
标签:UOJ577 SAM ULR 题解 len int 等价 ms mr

题解 UOJ577【[ULR #1] 打击复读

reference

https://www.cnblogs.com/crashed/p/17382894.html

https://www.cnblogs.com/sizeof127/articles/17579027.html

字符串——黄建恒,广东实验中学

题目描述

为了提升搜索引擎的关键词匹配度以加大访问量,某些网站可能在网页中无意义复读大量关键词。

你设计了一种方法量化评价一个文本(字符串 $s$)的“复读程度”:

字符串 $s$ 的下标从 $1\sim n$ 标号,第 $i$ 个字符被赋予两个权值:左权值 $wl_i$ 和右权值 $wr_i$ ,代表该位置的重要度。

定义一个子串 $s[l,r]$ 的左权值 $vl(s[l,r])$ 为:其在原串中各个匹配的左端点的左权值 $wl$ 和;右权值 $vr(s[l,r])$ 为:其在原串中各个匹配的右端点的右权值 $wr$ 和。这里 $t$ 在 $s$ 中所有的匹配是 $\forall 1 \le i \le j \le n,s[i,j]=t$,我们把这样的 $i$ 和 $j$ 分别叫做一个匹配的左右端点。

定义一个子串 $s[l,r]$ 的复读程度是它的左权值右权值的乘积,即 $w(s[l,r])=vl(s[l,r])\cdot vr(s[l,r])$ 。

$s$ 的“复读程度”定义为所有子串复读程度的和,即:

$$\sum\limits_{i=1}^{|S|}\sum\limits_{j=i}^{|S|}w(s[i,j]).$$

根据网站文本抽样的复读程度限流,就可以达到打击无意义复读行为的目的。

隔壁生命科学实验室正在分析跳蚤的基因序列。他们对基因的复读情况很感兴趣,于是顺便把这个锅丢给了你。

基因片段可以被视作字符集为 $\{A,T,G,C\}$ 的字符串,你要求出给定的基因片段 $S$ 的复读程度。

有些时候,由于新的科学发现,某个位置 $u$ 的左权值 $wl_u$ 会相应修改为 $v$,修改过后你需要给出基因片段 $S$ 的新的复读程度。

由于答案很大,你只需要输出答案对 $2^{64}$ 取模后的结果。

对于所有数据,满足 $1\leq n,m\leq 5\times 10^5,0\leq wl_i,wr_i,v_i< 2^{64},1\leq u_i\leq n$。

基本子串结构

概念

你现在有一个巨大的字符串 \(S=\{S_1, S_2, \cdots, S_n\}\)。在上面,定义了 \(occ(s)\) 表示一个小字符串 \(s\) 在 \(S\) 中的出现次数。

定义 \(ext(s)\) 为一个最长的串满足 \(occ(ext(s))=ext(s)\),容易发现它存在且唯一。容易发现 \(ext(ext(s))=ext(s)\),由此将 \(ext(s)\) 相同的 \(s\) 拿出来(都是 \(S\) 的子串)称为一个等价类,\(ext(s)=s\) 的 \(s\) 为等价类的代表元。

以 \(l\) 为横坐标,\(r\) 为纵坐标,建立平面直角坐标系,一个点 \((l, r)\) 代表一个子串 \(S[l:r]\)。将所有等价类在直角坐标系中画出来,发现:

  • 同一个代表元对应的等价类会在图中出现 \(occ()\) 次。

    以下所有讨论,没有说明,都是一个本质不同代表元值讨论其中一个对应等价类。

    (对应的在 SAM 中的等价类概念改成为”状态“)

  • 所有等价类,都形成了阶梯型,左上角为阶梯的角,左上对齐,右下阶梯型。

    这是基于 \(ext(S[l:r])=S[L:R]\implies ext(S[l':r'])=S[L, R](L\leq l'\leq l\leq r\leq r'\leq R)\) 这个直观的结论。

例子的来源是第一篇参考资料。字符串 aababcd 的等价类形态如图所示。同色的是一个等价类。

对应 SAM

每个等价类的一行的 \(r\) 相同,代表了正串 SAM 的一个状态,也即行等价类;每个等价类的一列的 \(l\) 相同,代表了反串 SAM 的一个状态,也即列等价类;本质相同的行等价类或列等价类对应的状态相同。

将每一行对应正 SAM 一个状态,每一列对应反 SAM 一个状态,则所有本质不同等价类的周长和为 \(O(n)\)。

以正 SAM 为例,对一个行等价类与其对应的状态 \(u\)。状态 \(u\) 跳满足 \(len_u+1=len_v, v\in ch_u\) 的 DAG 转移边代表行等价类向上跳(这里有个分讨:如果这个行等价类是代表元,则向上跳会跳到其他若干个等价类的底部;否则只有一个出边,跳到它上面一行。现在说明为什么非代表元只有一个出边,因为首先由 \(occ()\) 相同,向上的这一条边必然存在,然后如果有其它的边,\(occ(u)\) 与其上一行的 \(occ()\) 就不能相等了。例如 \(s,s+c, s+d\) 都存在,那么如果 \(occ(s)=occ(s+c)\),将 \(occ(s+d)\) 抠掉 \(d\) 之后剩下的一堆 \(s\) 的出现后面都是 \(d\),而不是 \(c\),\(occ(s)\) 就会凭空多出一些。)

状态 \(u\) 跳后缀链接 \(link_u\),代表行等价类往右走,因为它们都有相同的这个行也就是 \(rpos\) 也就是 \(endpos\)。(明显看出这个不严谨,摆烂了)

反 SAM 就是反的,列等价类向左或者向下。

SAM 前插字符

为了铺垫下文引入这个技巧。考虑在正 SAM 上匹配字符串的过程,如果现在跑到状态 \(u\),匹配长度 \(l\),那么怎么在前面插入字符 \(r\) 呢?首先确定是要在后缀链接上搞事情,然后:

  • 若 \(l =len_u\),即将要向后缀链接树上的一个儿子走,考虑这个儿子是 \(son[u][r]\),我们需要对每个状态都 \(son[link_u][S[pos_u-len_{link_u}]]\) 其中 \(pos_u\) 是 \(u\) 任意一个 endpos(插入的时候记一个,复制的时候复制过去),这样就是说在考虑 \(pos_u\) 这一 endpos 的时候将 \(u\) 代表的最短串的第一个字符拿出来,相当于就是后缀链接树上走下去第一个字符,然后就对了。
  • 否则判断是否有 \(S[pos_u-l]=r\),没有就失配,否则状态不变,长度 +1。

写的迷糊,参考 P4218 [CTSC2010] 珠宝商 这题题解。

建立结构

考虑同时建立正反串 SAM。识别代表元时,发现出边中 \(occ()\) 没有相同的就是代表元,它在保留 \(len_u+1=len_v\) 边的 DAG 的头上会有一条链,是这个等价类的行等价类或列等价类。然后对应另一个 SAM,只需要将其在另一个 SAM 上定位,或者采取以下做法:

  • dfs(x, y, l) 表示正在对应正 SAM 上 \(x\),反 SAM 上 \(y\),目前长度为 \(l\)。

  • 记正 SAM 为 \(ms\),反 SAM 为 \(mr\)。若 \(ms.len_x=mr.len_y\),找到代表元,记录一下。

    这里说明为什么一定是,这个意思是在进入新的等价类时,\(x\) 一直向上跳,\(y\) 因为跳后缀链接 \(l\) 不够一直不动,\(ms.len_x\) 在增,\(mr.len_y\) 不动且 \(ms.len_x\leq mr.len_y\),取等时就找到了代表元。

  • 然后遍历所有 \(ms.len_x+1=ms.len_v\) 出边,在 \(y\) 前对应前插一个字符,如果都存在,那么 dfs 下去,长度 +1。

找到代表元以后,由于剩下的等价类已经划分为很多条链(而且甚至删掉代表元以后 DAG 不用删 \(len_u+1\neq len_v\) 都只有一条出边),直接倒拓扑序(编号倒序)遍历上传代表元,暴力记录等价类的所有行等价类与列等价类。

solution

建立基本子串结构。明显是求

code

#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
typedef long long LL;
typedef unsigned long long mint;
template <int N, int M>
struct suffixam {
  int ch[N << 1][M], tot, link[N << 1], len[N << 1], occ[N << 1], pos[N << 1];
  int pr[N << 1][M];
  suffixam() : tot(1) {
    len[1] = link[1] = 0;
    memset(ch[1], 0, sizeof ch[1]);
  }
  int split(int p, int q, int r) {
    if (len[q] == len[p] + 1) return q;
    int u = ++tot;
    len[u] = len[p] + 1;
    memcpy(ch[u], ch[q], sizeof ch[0]);
    link[u] = link[q];
    link[q] = u;
    pos[u] = pos[q];
    for (; p && ch[p][r] == q; p = link[p]) ch[p][r] = u;
    return u;
  }
  int stc[N + 10];
  int expand(int p, int r) {
    if (ch[p][r]) return split(p, ch[p][r], r);
    int u = ++tot;
    pos[u] = len[u] = len[p] + 1;
    stc[len[u]] = r;
    memset(ch[u], 0, sizeof ch[u]);
    for (; p; p = link[p]) {
      if (ch[p][r]) {
        link[u] = split(p, ch[p][r], r);
        return u;
      } else {
        ch[p][r] = u;
      }
    }
    link[u] = 1;
    return u;
  }
  template <bool rev>
  vector<int> bucketsort() {
    vector<int> per(tot), buc(tot + 1);
    for (int i = 1; i <= tot; i++) buc[len[i]] += 1;
    for (int i = 1; i <= tot; i++) buc[i] += buc[i - 1];
    for (int i = 1; i <= tot; i++) per[--buc[len[i]]] = i;
    if (rev) reverse(per.begin(), per.end());
    return per;
  }
  int push_back(int u, int r, int l) {
    return ch[u][r];
  }
  int push_front(int u, int r, int l) {
    if (l == len[u]) return pr[u][r];
    return (stc[pos[u] - l] == r) * u;
  }
  int bel[N << 1];
};
int to_index(char t) {
  switch (t) {
    case 'A':
      return 0;
    case 'T':
      return 1;
    case 'G':
      return 2;
    case 'C':
      return 3;
  }
  throw "to_index: t is not in 'ATGC'";
}
int n, str[500010], blk, repr[1000010][2], inp[500010];
vector<int> sid[1000010], rid[1000010];
mint wl[500010], wr[500010], wrs[1000010], coe[1000010], tmp[1000010], hjh[500010];
suffixam<500010, 4> ms, mr;
void init() {
  for (int i = 1, lst = 1; i <= n; i++)
    ms.occ[lst = ms.expand(lst, str[i])] += 1, wrs[lst] += wr[i];
  for (int i = 1; i <= ms.tot; i++)
    ms.pr[ms.link[i]][ms.stc[ms.pos[i] - ms.len[ms.link[i]]]] = i;
  for (int i : ms.bucketsort<true>()) 
    ms.occ[ms.link[i]] += ms.occ[i], wrs[ms.link[i]] += wrs[i];
  for (int i = n, lst = 1; i >= 1; i--) 
    mr.occ[inp[i] = lst = mr.expand(lst, str[i])] += 1;
  for (int i = 1; i <= mr.tot; i++)
    mr.pr[mr.link[i]][mr.stc[mr.pos[i] - mr.len[mr.link[i]]]] = i;
  for (int i : mr.bucketsort<true>()) 
    mr.occ[mr.link[i]] += mr.occ[i];
  auto dfs = [&](auto& dfs, int x, int y, int len) -> void {
    if (ms.len[x] == mr.len[y]) {
      ms.bel[x] = mr.bel[y] = ++blk;
      repr[blk][0] = x, repr[blk][1] = y;
    }
    for (int r = 0; r < 4; r++) {
      int xv = ms.push_back(x, r, len), yv = mr.push_front(y, r, len);
      if (xv && yv && ms.len[xv] == ms.len[x] + 1) dfs(dfs, xv, yv, len + 1);
    }
  };
  dfs(dfs, 1, 1, 0);
  for (int u = ms.tot; u >= 1; u--) {
    if (!ms.bel[u]) {
      for (int r = 0; r < 4; r++) {
        if (ms.ch[u][r] && ms.len[u] + 1 == ms.len[ms.ch[u][r]]) { ms.bel[u] = ms.bel[ms.ch[u][r]]; break; }
      }
    }
    sid[ms.bel[u]].push_back(u);
  }
  for (int u = mr.tot; u >= 1; u--) {
    if (!mr.bel[u]) {
      for (int r = 0; r < 4; r++) {
        if (mr.ch[u][r] && mr.len[u] + 1 == mr.len[mr.ch[u][r]]) { mr.bel[u] = mr.bel[mr.ch[u][r]]; break; }
      }
    }
    rid[mr.bel[u]].push_back(u);
  }
  for (int id = 1; id <= blk; id++) {
    mint occ = ms.occ[repr[id][0]];
    tmp[0] = 0;
    for (int i = 1; i <= sid[id].size(); i++) tmp[i] = tmp[i - 1] + wrs[sid[id][i - 1]] * occ;
    auto getlen = [&](int u) { return mr.len[u] - mr.len[mr.link[u]]; };
    for (int i = 0; i < rid[id].size(); i++) coe[rid[id][i]] += tmp[getlen(rid[id][i])];
  }
  for (int i : mr.bucketsort<false>()) coe[i] += coe[mr.link[i]];
//for (int i = 1; i <= mr.tot; i++) for (int r = 0; r < 4; r++) if (mr.ch[i][r]) coe[mr.ch[i][r]] += coe[i], assert(mr.ch[i][r] > i);
  for (int i = 1; i <= n; i++) hjh[i] = coe[inp[i]];
}
int main() {
#ifndef LOCAL
  cin.tie(nullptr)->sync_with_stdio(false);
#endif
  int q;
  cin >> n >> q;
  for (int i = 1; i <= n; i++) {
    char ch;
    cin >> ch;
    str[i] = to_index(ch);
  }
  for (int i = 1; i <= n; i++) cin >> wl[i];
  for (int i = 1; i <= n; i++) cin >> wr[i];
  init();
  mint fans = 0;
  for (int i = 1; i <= n; i++) fans += wl[i] * hjh[i];
  cout << fans << endl;
  while (q--) {
    int i;
    mint v;
    cin >> i >> v;
    fans -= wl[i] * hjh[i];
    wl[i] = v;
    fans += wl[i] * hjh[i];
    cout << fans << endl;
  }
  return 0;
}

标签:UOJ577,SAM,ULR,题解,len,int,等价,ms,mr
From: https://www.cnblogs.com/caijianhong/p/18153828/solution-UOJ577

相关文章

  • 编译用于Qt的opencv问题解决
    CMakewasunabletofindabuildprogramcorrespondingto"MinGWMakefiles"解释:这个错误表明CMake无法找到用于生成Makefiles的构建程序。在使用CMake生成项目文件时,如果指定了"MinGWMakefiles",CMake需要一个Make工具来构建项目,而这个工具通常是由MinGW提供的。如......
  • macOS配置Clion用于STM32开发找不到stdint.h等头文件问题解决方案
    问题编译工程时发现出现大量类似错误如下/opt/homebrew/Cellar/arm-none-eabi-gcc/13.2.0/lib/gcc/arm-none-eabi/13.2.0/include/stdint.h:9:16:fatalerror:stdint.h:Nosuchfileordirectory问题原因不能使用brewinstallarm-none-eabi-gcc安装编译工具链[1]解决方......
  • 「洛谷」题解:P1720 月落乌啼算钱(斐波那契数列)
    题目传送门比较经典的一道斐波那契数列的模版题,原题中给了一个很复杂的公式(也就是下面这个),但是实际上题目跟它毛关系没有……(所以放这个公式干什么)\[F_n=\dfrac{\left(\frac{1+\sqrt{5}}{2}\right)^n-\left(\frac{1-\sqrt{5}}{2}\right)^n}{\sqrt{5}}\]看见题解去了有很多人都......
  • AtCoder Beginner Contest 350 A - G 题解
    AtCoderBeginnerContest350A-PastABCsSolution把最后三个字符转成数字判断即可Code#include<bits/stdc++.h>usingnamespacestd;intmain(){strings;cin>>s;s=s.substr(3,3);intx=0;x=(s[0]-'0')*100+(s[1]-�......
  • ABC350C 题解
    怎么赛时连这道都不会了/ll注意到输入是个排列,这意味着我们可以直接确定每个元素应在的位置。考虑维护每个数当前所在的位置\(\{p\}\)。对于任意\(i\in[1,n]\),我们访问\(p_i\),如果该位置不为第\(i\)位便对排列中第\(i\)位的数\(j\)和\(i\)进行“交换”操作。“......
  • P1763 埃及分数 题解
    题目传送门【-1】前言这题的剪枝真的太妙了,很难想象巨佬是怎么独立想出来这所有的剪枝的。本题解没有包含所有的剪枝,只选了我认为最好理解的几条剪枝。想学习所有的剪枝的右转巨佬的题解。【1】本题大框架:迭代加深搜索(IDDFS)看到\(1<a<b<1000\),可以猜测分数的个数不会......
  • initialize方法重定向无限循环问题解决方案
    由于在initialize方法中进行重定向而造成的重定向循环。当session('?user_id')检查失败时,你的代码会尝试重定向到登录页面。如果登录页面或者处理登录的控制器也继承自同一个基类(或者有类似的initialize检查),这将导致每次尝试访问登录页面时都会再次执行重定向,从而陷入无限循......
  • helpdesk桌面运维常见问题解决
    helpdesk是一套帮助IT团队管理IT工单生命周期、自动化日常工作、优化工作流程的软件或软件集合,它可以帮助IT团队提高生产力、降低成本、改善服务水平和客户体验。 在现代企业中,helpdesk桌面运维是一项至关重要的工作,helpdesk团队负责处理员工或客户在日常工作中遇到的各种技术......
  • 题解 CF1743F【Intersection and Union】
    postedon2022-10-2119:23:54|under题解|sourceproblem给定\(n\)个集合\(S_i\),以\(l_i,r_i\)的形式给出,集合的元素就是\(\{x|x\in[l_i,r_i]\cap\mathbb{N}\}\)。有三种集合间的二元运算,分别是交(\(\cap\))、并(\(\cup\))、对称差(\(\oplus\))。其中对称差(\(A\oplusB......
  • 【微电平台】-高并发实战经验-奇葩问题解决及流程优化之旅
    微电平台微电平台是集电销、企业微信等于一体的综合智能SCRMSAAS化系统,涵盖多渠道管理、全客户生命周期管理、私域营销运营等主要功能,承接了京东各业务线服务,专注于为业务提供职场外包式的一站式客户管理及一体化私域运营服务。 导读本文介绍电销系统【客户名单离线打标......