(2:00)OID:“完了,蓝屏了!”(代码全消失)
众人欢呼
OID:开机,“原题测试……“
(30min later)OID 开始传统艺能: “

***

∗∗∗又AK了,承认自己强很难吗?……狗把人吃!……”
众人大汗,感到不妙……

众所周知,如果一个选手在比赛时提前码完了,AK 了,他就会觉得这场比赛简单,推己及人,觉得别人都 AK 了,于是自以为揭破真相似的对别人说:“你 AK 了”,这是传统艺能。因此,大多私人模拟赛都建议“AK 了不要大声喧哗”,顺便,如果见到有选手放骚话,搞传统艺能,那么你可以放心的认为ta AK 了。

是的,即便是开考两个小时后丢失所有代码,OID 还是不可阻挡地 AK 了。


而此时,我才看到这道题:

题面

给定一个由’A’,‘B’,'C’三种字母组成的字符串,你需要支持两种操作:

  • 1 p c :将位置

    p

    p

    p 上的字符改为

    c

    c

    c,其中

    c

    c

    c 为’A’,‘B’,'C’其中之一,有可能该位置的字符没有变化。

  • 2 l r :求出子串

    [

    l

    ,

    r

    ]

    [l,r]

    [l,r] 的本质不同子序列个数(不含空子序列)。

s

s

s 是串

t

t

t 的子序列当且仅当串

s

s

s 可以通过删去

t

t

t 中的某些字符得到。

两个子序列本质不同当且仅当它们长度不同或存在一个位置使得该位置上两个子序列的字符不同。

字符串长度

n

n

n ,操作数

m

m

m ,

n

,

m

1

0

5

n,m\leq10^5

n,m≤105 。


题解

初看到这道题可能会感到无力,毕竟条件给得太复杂了。

我们可以先想想平方做法。

如果只是给你一个串,让你求本质不同的子序列个数(字符集很小),你会怎么做?

我们会用子序列自动机 !子序列自动机的任意一条从源点出发的路径都可以唯一标识一个子序列。所以,我们把子序列自动机建出来,再求路径个数。

子序列自动机的构造方法很简单,每个位置继承右边位置的所有邻接点后,再把右边相邻点拿去替换某个邻接点。单次时间复杂度

O

(

S

Σ

)

O(|S|\cdot|\Sigma|)

O(∣S∣⋅∣Σ∣) ,考虑这道题的字符集大小是 3,总时间复杂度近似

O

(

n

m

)

O(nm)

O(nm) 。

接下来从子序列自动机的构造上着手优化。不少人应该意识到了,子序列自动机的构造非常简洁单调,非常傻,而且我们只需要路径个数,路径个数又是建自动机的过程中就可以求出来的——我们甚至没必要存下这个自动机。

所以经过深思熟虑,我们可以用矩阵乘法来标准化这一过程!

具体地,我们本来维护邻接点,但是实际上邻接点是谁不重要,我们只需要知道该邻接点出发的路径个数,即当前点走 ABC 分别能走到的路径个数。

我们令这三个值分别为

d

p

A

,

d

p

B

,

d

p

C

dp_A,dp_B,dp_C

dpA​,dpB​,dpC​ ,放到一个矩阵里:

[

d

p

A

d

p

B

d

p

C

]

\left[\begin{matrix} dp_A\\ dp_B\\ dp_C\\ \end{matrix}\right]

⎣⎡​dpA​dpB​dpC​​⎦⎤​ 。

当前点的转移非常傻,假如它是 A ,那么就会令

d

p

A

=

d

p

A

+

d

p

B

+

d

p

C

+

1

dp'_A=dp_A+dp_B+dp_C+1

dpA′​=dpA​+dpB​+dpC​+1,其余不变。为了方便,我们添上一个齐次项 1,那么转移就是这样:

[

1

1

1

1

0

1

0

0

0

0

1

0

0

0

0

1

]

×

[

d

p

A

d

p

B

d

p

C

1

]

=

[

d

p

A

d

p

B

d

p

C

1

]

\left[\begin{matrix} 1&1&1&1\\ 0&1&0&0\\ 0&0&1&0\\ 0&0&0&1\\ \end{matrix}\right] \times \left[\begin{matrix} dp_A\\ dp_B\\ dp_C\\ 1 \end{matrix}\right] =\left[\begin{matrix} dp'_A\\ dp'_B\\ dp'_C\\ 1 \end{matrix}\right]

⎣⎢⎢⎡​1000​1100​1010​1001​⎦⎥⎥⎤​×⎣⎢⎢⎡​dpA​dpB​dpC​1​⎦⎥⎥⎤​=⎣⎢⎢⎡​dpA′​dpB′​dpC′​1​⎦⎥⎥⎤​

BC 是类似的。

然后我们就可以把矩阵放到线段树上,维护区间乘积。时间复杂度

O

(

n

log

n

)

O(n\log n)

O(nlogn),常数 64 。

CODE

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define LL long long
#define DB double
#define lowbit(x) (-(x) & (x))
#define ENDL putchar('\n')
#define FI first
#define SE second
//#pragma GCC optimize(2)
int xchar() {
static const int maxn = 100000;
static char b[maxn];
static int len = 0,pos = 0;
if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
if(pos == len) return -1;
return b[pos ++];
}
//#define getchar() xchar()
LL read() {
LL f=1,x=0;int s = getchar();
while(s < '0' || s > '9') {if(s<0) return -1;if(s=='-')f = -f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s^48);s = getchar();}
return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar('0'+(x%10));}
void putnum(LL x) {
if(!x) {putchar('0');return ;}
if(x<0) {putchar('-');x = -x;}
return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);} const int MOD = 998244353;
int n,m,s,o,k;
struct mat{
int n,m;
int s[4][4];
mat(){n=m=4;memset(s,0,sizeof(s));}
mat(int a,int b,int c) {
n=a;m=b;memset(s,0,sizeof(s));
for(int i = 0;i < n;i ++) s[i][i] = c;
}
}nm1(4,4,1);
mat ts[3];
mat operator * (mat a,mat b) {
mat c(a.n,b.m,0);
for(int i = 0;i < c.n;i ++) {
for(int k = 0;k < a.m;k ++) {
if(a.s[i][k])
for(int j = 0;j < c.m;j ++) {
c.s[i][j] += a.s[i][k] *1ll* b.s[k][j] % MOD;
if(c.s[i][j] >= MOD) c.s[i][j] -= MOD;
}
}
}return c;
}
char a[MAXN];
mat tre[MAXN<<2];
int M;
void maketree(int n) {
M=1;while(M<n+2)M<<=1;
for(int i = 1;i <= n;i ++) {
tre[M+i] = ts[a[i]-'A'];
}
for(int i = M-1;i > 0;i --) tre[i] = tre[i<<1|1] * tre[i<<1];
}
void addtree(int x,char y) {
tre[M+x] = ts[y-'A'];
for(int s = (M+x)>>1;s>0;s >>= 1) tre[s] = tre[s<<1|1] * tre[s<<1];
return ;
}
mat findtree(int l,int r) {
mat ls = nm1,rs = nm1;
if(l > r) return nm1;
for(int s=M+l-1,t=M+r+1;(s>>1) != (t>>1);s >>= 1,t >>= 1) {
if(!(s&1)) ls = tre[s^1] * ls;
if(t & 1) rs = rs * tre[t^1];
}return rs * ls;
}
int main() {
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
ts[0] = ts[1] = ts[2] = nm1;
for(int i = 0;i < 3;i ++) {
for(int j = 0;j < 4;j ++) ts[i].s[j][i] = 1;
}
n = read(); m = read();
scanf("%s",a + 1);
maketree(n);
for(int i = 1;i <= m;i ++) {
k = read();
if(k == 1) {
s = read(); char cc[3];
scanf("%s",cc);
addtree(s,cc[0]);
}
else {
s = read();o = read();
mat tr = findtree(s,o),as(1,4,0);
as.s[0][3] = 1;
as = as * tr;
int ans = (0ll+ as.s[0][0] + as.s[0][1] + as.s[0][2]) % MOD;
AIput(ans,'\n');
}
}
return 0;
}

[NOI P模拟赛] 传统艺能(子序列自动机、矩阵乘法,线段树)的更多相关文章

  1. 【NOI P模拟赛】最短路(树形DP,树的直径)

    题面 给定一棵 n n n 个结点的无根树,每条边的边权均为 1 1 1 . 树上标记有 m m m 个互不相同的关键点,小 A \tt A A 会在这 m m m 个点中等概率随机地选择 k k k ...

  2. 计蒜客模拟赛 #5 (B 题) 动态点分治+线段树

    虽然是裸的换根dp,但是为了在联赛前锻炼码力,强行上了点分树+线段树. 写完+调完总共花了不到 $50$ 分钟,感觉还行. code: #include <bits/stdc++.h> # ...

  3. [NOIP10.5模拟赛]1.a题解--离散化+异或线段树

    题目链接: 咕咕咕 https://www.luogu.org/problemnew/show/CF817F 闲扯 在Yali经历几天折磨后信心摧残,T1数据结构裸题考场上连暴力都TM没打满 分析 观 ...

  4. [BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)

    题目描述 给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border . Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+ ...

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

    题目描述 给出 $S$ 串和 $m$ 个 $T_i$ 串,$q$ 次询问,每次询问给出 $l$ .$r$ .$x$ .$y$ ,求 $S_{x...y}$ 在 $T_l,T_{l+1},...,T_r ...

  6. 【bzoj1444】[Jsoi2009]有趣的游戏 AC自动机+矩阵乘法

    题目描述 输入 注意 是0<=P 输出 样例输入 样例输出 题解 AC自动机+矩阵乘法 先将所有字符串放到AC自动机中,求出Trie图. 然后构建邻接矩阵:如果x不是某个字符串的末位置,则x连向 ...

  7. 2018 ACMICPC上海大都会赛重现赛 H - A Simple Problem with Integers (线段树,循环节)

    2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 H - A Simple Problem with Integers (线段树,循环节) 链接:https://ac.nowcoder.co ...

  8. [BZOJ 1009] [HNOI2008] GT考试 【AC自动机 + 矩阵乘法优化DP】

    题目链接:BZOJ - 1009 题目分析 题目要求求出不包含给定字符串的长度为 n 的字符串的数量. 既然这样,应该就是 KMP + DP ,用 f[i][j] 表示长度为 i ,匹配到模式串第 j ...

  9. 【POJ2778】AC自动机+矩阵乘法

    DNA Sequence Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 14758 Accepted: 5716 Descrip ...

随机推荐

  1. 【SignalR全套系列】之在.Net Core 中实现SignalR实时通信

    ​ 微信公众号:趣编程ACE 关注可了解更多的.NET日常实战开发技巧,如需源码 请公众号后台留言 源码 [如果觉得本公众号对您有帮助,欢迎关注] 前文回顾 [SignalR全套系列]之在.NetCo ...

  2. split(),strip,split("/")[-1] 和 split("/",-1)的区别

    Python中split()函数,通常用于将字符串切片并转换为列表. 一.函数说明: split():语法: str.split(str="",num=string.count(s ...

  3. Bika LIMS 开源LIMS集—— SENAITE的安装

    安装环境 操作系统 Ubuntu 18.04 LTS Python 2.x. Plone 4 安装步骤 Ubuntu等Linux.Mac系统一般安装有Python的环境,但由于需要安装Python扩展 ...

  4. Vue是怎么渲染template内的标签内容的?

    我们在使用Vue做项目时,都会用到脚手架,相应的我们会在template写标签内容.那么你知道为什么会在template写标签吗?这当中经过了怎样的处理呢? <template> < ...

  5. ​​​​​​​ARCGIS API for Python进行城市区域提取

    ​ArcGIS API for Python主要用于Web端的扩展和开发,提供简单易用.功能强大的Python库,以及大数据分析能力,可轻松实现实时数据.栅格数据.空间数据等多源数据的接入和GIS分析 ...

  6. PTA(BasicLevel)-1031 查验身份证

    一.问题定义 一个合法的身份证号码由17位地区.日期编号和顺序编号加1位校验码组成.校验码的计算规则如下:首先对前17位数字加权求和,权重分配为:{7,9,10,5,8,4,2,1,6,3,7,9,1 ...

  7. Unity3D学习笔记8——GPU实例化(3)

    目录 1. 概述 2. 详论 2.1. 自动实例化 2.2. MaterialPropertyBlock 3. 参考 1. 概述 在前两篇文章<Unity3D学习笔记6--GPU实例化(1)&g ...

  8. C++对象的应用

    本篇文章将介绍对象数组,对象的动态分配以及对象在函数中的应用. 一.对象数组 1.对象数组的定义和初始化 定义对象数组与定义普通数组的语法形式基本相同.如定义一个Square obj[3]:表示一个正 ...

  9. 5-4 Sentinel 限流_流控与降级

    Sentinel 介绍 什么是Sentinel Sentinel也是Spring Cloud Alibaba的组件 Sentinel英文翻译"哨兵\门卫" 随着微服务的流行,服务和 ...

  10. 4-3 Spring MVC框架-02

    Spring MVC框架-02 Ⅰ.RESTful基础 是一种设计风格和开发方式 1.get和post请求区别: get post 获取请求 上传请求 请求参数在地址栏URL 请求参数在请求体里面 U ...