题面

记忆犹新

题解

f

f

f 函数值给得非常明显,一看就给人一种熟悉感——这不是连分数吗?

众所周知,连分数有个递推公式,即

p

i

=

a

i

p

i

1

+

p

i

2

q

i

=

a

i

q

i

1

+

q

i

2

p_i=a_ip_{i-1}+p_{i-2}\\ q_i=a_iq_{i-1}+q_{i-2}

pi​=ai​pi−1​+pi−2​qi​=ai​qi−1​+qi−2​

而且不用管约分,因为根据这个式子算出来的一定是最简分数。

可以发现两者的转移式子几乎一模一样,只是初值不同。

于是,一种方式是用矩阵维护转移,分子分母的转移式子相同,只用存一份。

这样就可以解决只有 APPEND 操作的数据了。

但是我们发现,如果把转移跟

a

a

a 序列绑定在一起,很不好搞。我们其实可以把转移和操作序列绑定:

我们把向量矩阵定义为

(

p

i

,

p

i

1

,

p

i

2

)

(p_i,p_{i-1},p_{i-2})

(pi​,pi−1​,pi−2​) 和

(

q

i

,

q

i

1

,

q

i

2

)

(q_i,q_{i-1},q_{i-2})

(qi​,qi−1​,qi−2​) ,我们不妨从

p

p

p 入手,翻译操作类型代表的矩阵,

  • W 类型:最后一个+1,也就是

    p

    i

    +
     ⁣ ⁣

    =

    p

    i

    1

    p_i+\!\!=p_{i-1}

    pi​+=pi−1​ ,这个可以转化为矩阵

    (

    1

    0

    0

    1

    1

    0

    0

    0

    1

    )

    \left(\begin{matrix}1&0&0\\1&1&0\\0&0&1\end{matrix}\right)

    ⎝⎛​110​010​001​⎠⎞​ 。

E 类型,分两种小情况:

  • WE:前面是个 W ,保证了

    a

    a

    a 序列最后一个元素大于 1,那么把它减 1,再添两个 1,此时的 E转移矩阵为

    (

    2

    1

    1

    1

    0

    1

    0

    0

    0

    )

    \left(\begin{matrix}2&1&1\\-1&0&-1\\0&0&0\end{matrix}\right)

    ⎝⎛​2−10​100​1−10​⎠⎞​ 。

  • _E:除了前面是 W 之外的情况,容易发现

    a

    a

    a 序列最后一个元素必定为 1,那么把倒数第二个数+1,此时比较麻烦,要牵扯到

    p

    i

    2

    p_{i-2}

    pi−2​ ,这也是我定义

    3

    ×

    3

    3\times3

    3×3 矩阵的原因。从递推公式上算,转移矩阵应该是

    (

    1

    0

    0

    0

    1

    0

    1

    1

    1

    )

    \left(\begin{matrix}1&0&0\\0&1&0\\1&1&1\end{matrix}\right)

    ⎝⎛​101​011​001​⎠⎞​ 。

为了方便,我们算出

W

E

:

(

2

1

1

1

0

1

0

0

0

)

=

X

:

(

1

0

1

0

1

1

0

0

0

)

×

_

E

:

(

1

0

0

0

1

0

1

1

1

)

{\tt WE}:\left(\begin{matrix}2&1&1\\-1&0&-1\\0&0&0\end{matrix}\right)={\tt X}:\left(\begin{matrix}1&0&1\\0&1&-1\\0&0&0\end{matrix}\right)\times{\tt\_E}:\left(\begin{matrix}1&0&0\\0&1&0\\1&1&1\end{matrix}\right)

WE:⎝⎛​2−10​100​1−10​⎠⎞​=X:⎝⎛​100​010​1−10​⎠⎞​×_E:⎝⎛​101​011​001​⎠⎞​ ,用这个

X

{\tt X}

X 矩阵进行 WE 的计算,那么相当于所有的 E 都先看成是 _E 类型,然后若出现了 WE ,就在 WE 中间乘上矩阵

X

\tt X

X 。

现在,该有的推理都已经结束了,开始我们的平衡树之旅了。

平衡树中要记录两个 char 表示左右端点的字符,四个矩阵,分别记录无颜色翻转无旋转无颜色翻转有旋转有颜色翻转无旋转有颜色翻转有旋转这四种情况下的区间转移矩阵,还有懒标记,以及平衡树必有的 sonsiz,和随机值。

CODE

为了使

3

×

3

3\times3

3×3 的矩阵过掉,卡常数颇费心思。

#include<map>
#include<cmath>
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x) & (x))
LL read() {
LL f = 1,x = 0;char s = getchar();
while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
return f * x;
}
void putint(int x) {
if(!x) return ;
putint(x/10);putchar(x%10+'0');
return ;
}
const int MOD = 998244353;
int n,m,i,j,s,o,k;
int Q,X = 123127;
int my_rand() {
return X = (X *2333ll + 2344243ll) % MOD;
}
char ss[MAXN<<1];
struct mat{
int n,m;
int s[3][3];
mat(){n=m=0;}
mat(int x) {n=m=3;memset(s,0,sizeof(s));for(int i = 0;i < 3;i ++)s[i][i]=x;}
void set(int N,int M) {n=N;m=M;memset(s,0,sizeof(s));}
}Ap,Aq,W,WE,_E,nm1;
void MD(int &x) {if(x>=MOD)x-=MOD;}
// p_-2 = 0, p_-1 = 1; q_-2 = 1, q_-1 = 0;
mat operator * (mat a,mat b) {
mat c;c.n = a.n;c.m = b.m;
c.s[0][0] = (a.s[0][0]*1ll*b.s[0][0]+a.s[0][1]*1ll*b.s[1][0]+a.s[0][2]*1ll*b.s[2][0]) % MOD;
c.s[0][1] = (a.s[0][0]*1ll*b.s[0][1]+a.s[0][1]*1ll*b.s[1][1]+a.s[0][2]*1ll*b.s[2][1]) % MOD;
c.s[0][2] = (a.s[0][0]*1ll*b.s[0][2]+a.s[0][1]*1ll*b.s[1][2]+a.s[0][2]*1ll*b.s[2][2]) % MOD; c.s[1][0] = (a.s[1][0]*1ll*b.s[0][0]+a.s[1][1]*1ll*b.s[1][0]+a.s[1][2]*1ll*b.s[2][0]) % MOD;
c.s[1][1] = (a.s[1][0]*1ll*b.s[0][1]+a.s[1][1]*1ll*b.s[1][1]+a.s[1][2]*1ll*b.s[2][1]) % MOD;
c.s[1][2] = (a.s[1][0]*1ll*b.s[0][2]+a.s[1][1]*1ll*b.s[1][2]+a.s[1][2]*1ll*b.s[2][2]) % MOD; c.s[2][0] = (a.s[2][0]*1ll*b.s[0][0]+a.s[2][1]*1ll*b.s[1][0]+a.s[2][2]*1ll*b.s[2][0]) % MOD;
c.s[2][1] = (a.s[2][0]*1ll*b.s[0][1]+a.s[2][1]*1ll*b.s[1][1]+a.s[2][2]*1ll*b.s[2][1]) % MOD;
c.s[2][2] = (a.s[2][0]*1ll*b.s[0][2]+a.s[2][1]*1ll*b.s[1][2]+a.s[2][2]*1ll*b.s[2][2]) % MOD;
return c;
}
struct it{
char L,R;char f1,f2;
mat m[2][2];
it() {L=R=0;f1=f2=0;}
it(int x){f1=f2=0;m[0][0]=m[0][1]=m[1][0]=m[1][1]=mat(1);f1=f2=0;}
void flip() {
if(L=='E') L='W'; else L='E';
if(R=='E') R='W'; else R='E';
f1 ^= 1;
}
void reverse() {
swap(L,R);f2 ^= 1;
}
}tre[(MAXN*2)<<2]={it(1)},nmb[(MAXN*2)<<2]={it(1)};
it merg(it a,it b) {
if(!a.L) return b; if(!b.L) return a;
it c;c.L = a.L;c.R = b.R;
if(a.R=='W'&&b.L=='E') c.m[0][0] = a.m[a.f1][a.f2]*WE*b.m[b.f1][b.f2],c.m[1][1] = b.m[1^b.f1][1^b.f2]*WE*a.m[1^a.f1][1^a.f2];
else c.m[0][0] = a.m[a.f1][a.f2]*b.m[b.f1][b.f2],c.m[1][1] = b.m[1^b.f1][1^b.f2]*a.m[1^a.f1][1^a.f2]; if(a.R=='E'&&b.L=='W') c.m[0][1] = b.m[b.f1][1^b.f2]*WE*a.m[a.f1][1^a.f2],c.m[1][0] = a.m[1^a.f1][a.f2]*WE*b.m[1^b.f1][b.f2];
else c.m[0][1] = b.m[b.f1][1^b.f2]*a.m[a.f1][1^a.f2],c.m[1][0] = a.m[1^a.f1][a.f2]*b.m[1^b.f1][b.f2];
return c;
}
bool lz[(MAXN*2)<<2][2];
int sn[(MAXN*2)<<2][2],siz[(MAXN*2)<<2],hp[(MAXN*2)<<2],CNT;
// -----------------------------------------Treap!!!
struct np{
int s[2];np(){s[0]=s[1]=0;}
};
int update(int x) {
int ls = sn[x][0],rs = sn[x][1];
siz[x] = siz[ls] + siz[rs] + 1;
tre[x] = merg(tre[ls],merg(nmb[x],tre[rs]));
return x;
}
void pushdown(int a) {
int ls = sn[a][0],rs = sn[a][1];
if(lz[a][0]) {
tre[ls].flip(); tre[rs].flip();
nmb[ls].flip(); nmb[rs].flip();
lz[ls][0] ^= 1; lz[rs][0] ^= 1;
lz[a][0] = 0;
}
if(lz[a][1]) {
tre[ls].reverse(); tre[rs].reverse();
nmb[ls].reverse(); nmb[rs].reverse();
swap(sn[ls][0],sn[ls][1]); swap(sn[rs][0],sn[rs][1]);
lz[ls][1] ^= 1; lz[rs][1] ^= 1;
lz[a][1] = 0;
}return ;
}
int maketree(char C) {
int a = ++ CNT;
lz[a][0] = lz[a][1] = 0;
sn[a][0] = sn[a][1] = 0;
hp[a] = my_rand();
mat nw,nw2;
if(C == 'W') nw = W,nw2 = _E;
else nw = _E,nw2 = W;
tre[a].L = tre[a].R = C;
tre[a].m[0][0]=tre[a].m[0][1]=nw;
tre[a].m[1][0]=tre[a].m[1][1]=nw2;
nmb[a] = tre[a]; siz[a] = 1;
return a;
}
np Split(int x,int rk) {
np as = np();
if(!x) return as;
pushdown(x);
int d = (siz[sn[x][0]]+1 <= rk);
if(d) rk -= siz[sn[x][0]]+1;
as = Split(sn[x][d],rk);
sn[x][d] = as.s[d^1];
as.s[d^1] = update(x);
return as;
}
int Merge(int p1,int p2) {
if(!p1 || !p2) return p1+p2;
pushdown(p1); pushdown(p2);
if(hp[p1] < hp[p2]) {
sn[p1][1] = Merge(sn[p1][1],p2);
return update(p1);
}
sn[p2][0] = Merge(p1,sn[p2][0]);
return update(p2);
}
int Flip(int x,int l,int r) {
np p2 = Split(x,r);
np p1 = Split(p2.s[0],l-1);
int a = p1.s[1];
tre[a].flip();nmb[a].flip();
lz[a][0] ^= 1;
return Merge(Merge(p1.s[0],p1.s[1]),p2.s[1]);
}
int Reverse(int x,int l,int r) {
np p2 = Split(x,r);
np p1 = Split(p2.s[0],l-1);
int a = p1.s[1];
tre[a].reverse();nmb[a].reverse();
lz[a][1] ^= 1; swap(sn[a][0],sn[a][1]);
return Merge(Merge(p1.s[0],p1.s[1]),p2.s[1]);
}
//---------------------------------------Treap Forever!!!
int root = 0;
int main() {
nm1 = mat(1);
Ap.set(1,3); Aq.set(1,3);
W.set(3,3); WE.set(3,3); _E.set(3,3);
// p-1= 1, q-1= 0
// p0 = 0, q0 = 1;
// p1 = 1, q1 = 1;
Ap.s[0][0] = 1; Ap.s[0][1] = 0; Ap.s[0][2] = 1;
Aq.s[0][0] = 1; Aq.s[0][1] = 1; Aq.s[0][2] = 0; int tm1[3][3] = {{1,0,0},{1,1,0},{0,0,1}};
memcpy(W.s,tm1,sizeof(W.s)); int tm2[3][3] = {{1,0,1},{0,1,MOD-1},{0,0,0}};
memcpy(WE.s,tm2,sizeof(WE.s)); int tm3[3][3] = {{1,0,0},{0,1,0},{1,1,1}};
memcpy(_E.s,tm3,sizeof(_E.s)); n = read();Q = read();
scanf("%s",ss + 1);
for(int j = 1;j <= n;j ++) {
root = Merge(root,maketree(ss[j]));
} it as = tre[root];
mat sp = Ap*as.m[as.f1][as.f2],sq = Aq*as.m[as.f1][as.f2];
putint(sp.s[0][0]);putchar(' ');putint(sq.s[0][0]);ENDL;
while(Q --) {
char tps[15],cc;
scanf("%s",tps);
if(tps[0] == 'A') {
cc = getchar();
while(cc == ' ') cc = getchar();
n ++;
root = Merge(root,maketree(cc));
}
else if(tps[0] == 'F') {
s = read();o = read();
root = Flip(root,s,o);
}
else {
s = read();o = read();
root = Reverse(root,s,o);
}
it as = tre[root];
mat sp = Ap*as.m[as.f1][as.f2],sq = Aq*as.m[as.f1][as.f2];
putint(sp.s[0][0]);putchar(' ');putint(sq.s[0][0]);ENDL;
}
return 0;
}

过掉以后,发现由于序列特殊性,连分数其实可以用 2×2 的矩阵。

Stern-Brocot 树

有同学在考场上发现了规律,那就是:W 操作相当于在

Stern-Brocot

\texttt{Stern-Brocot}

Stern-Brocot 树上跳左子树,E 操作相当于跳右子树,于是,我们可以只在矩阵中记录两个值(当然分子分母要分开算):当前点左边的值和右边的值,往左跳相当于把左边的值加到右边,往右跳就把右边的数加到左边。

这二者的矩阵都非常好推,比连分数好推多了。

这个结论是怎么发现的呢?打表找规律

[NOI2021] 密码箱 (平衡树,连分数,Stern-Brocot 树,矩阵)的更多相关文章

  1. Wannafly Winter Camp 2019.Day 8 div1 E.Souls-like Game(线段树 矩阵快速幂)

    题目链接 \(998244353\)写成\(99824435\)然后调这个线段树模板1.5h= = 以后要注意常量啊啊啊 \(Description\) 每个位置有一个\(3\times3\)的矩阵, ...

  2. 从二叉查找树到平衡树:avl, 2-3树,左倾红黑树(含实现代码),传统红黑树

    参考:自平衡二叉查找树 ,红黑树, 算法:理解红黑树 (英文pdf:红黑树) 目录 自平衡二叉树介绍 avl树 2-3树 LLRBT(Left-leaning red-black tree左倾红黑树 ...

  3. CF719E(线段树+矩阵快速幂)

    题意:给你一个数列a,a[i]表示斐波那契数列的下标为a[i],求区间对应斐波那契数列数字的和,还要求能够维护对区间内所有下标加d的操作 分析:线段树 线段树的每个节点表示(f[i],f[i-1])这 ...

  4. 【Codeforces718C】Sasha and Array 线段树 + 矩阵乘法

    C. Sasha and Array time limit per test:5 seconds memory limit per test:256 megabytes input:standard ...

  5. Codeforces Round #337 (Div. 2) D. Vika and Segments 线段树 矩阵面积并

    D. Vika and Segments     Vika has an infinite sheet of squared paper. Initially all squares are whit ...

  6. 线段树 + 矩阵 --- ZOJ 3772 Calculate the Function

    Calculate the Function Problem's Link:   http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCod ...

  7. 【BZOJ】1146: [CTSC2008]网络管理Network(树链剖分+线段树套平衡树+二分 / dfs序+树状数组+主席树)

    http://www.lydsy.com/JudgeOnline/problem.php?id=1146 第一种做法(时间太感人): 第二种做法(rank5,好开心) ================ ...

  8. ZOJ3772 - Calculate the Function(线段树+矩阵)

    题目大意 给定一个序列A1 A2 .. AN 和M个查询 每个查询含有两个数 Li 和Ri. 查询定义了一个函数 Fi(x) 在区间 [Li, Ri] ∈ Z. Fi(Li) = ALi Fi(Li ...

  9. LOJ2980 THUSC2017大魔法师(线段树+矩阵乘法)

    线段树每个节点维护(A,B,C,len)向量,操作即是将其乘上一个矩阵. #include<iostream> #include<cstdio> #include<cma ...

随机推荐

  1. ElasticSearch6.4.2

    做一个简单的API记录 1.依赖为6.4.2  比较老的版本 2.指定ES集群,可接多个Put(); Setting setting=Setting.builder().put("clust ...

  2. 快速选择 第k个数

    快速选择 第k个数 题目描述 给定一个序列,求第k小的数 算法思想 利用快速排序思想,算法复杂度能达到O(n)步骤如下: 1.找到排序分界点x,这里选择区间最左值 2.排序,让左边的值都小于x,右边都 ...

  3. 面试突击58:truncate、delete和drop的6大区别

    在 MySQL 中,使用 truncate.delete 和 drop 都可以实现表删除,但它们 3 个的使用场景和执行效果完全不同,接下来我们来盘点一下. truncate.delete.drop区 ...

  4. 基于web3D展示技术的煤矿巷道3D可视化系统

    地下开采离不开巷道工程.煤矿的生产.运输.排水.通风等各个环节都少不了巷道的支持.在煤矿智能化建设被提上日程的今天,巷道工程的智能化.可视化建设也成了行业趋势.尤其是复杂的井下作业环境,人员信息安全问 ...

  5. Go写文件的权限 WriteFile(filename, data, 0644)?

    本文来自博客园,作者:阿伟的博客,转载请注明原文链接:https://www.cnblogs.com/cenjw/p/go-ioutil-writefile-perm.html 前言 go iouit ...

  6. React项目中 使用 CSS Module

    安装react-app-rewired 由于新的 react-app-rewired@2.x 版本的关系,还需要安装 customize-cra.但是我们这里不需要安装 react-app-rewir ...

  7. Flex & Bison 开始

    Flex 与 Bison 是为编译器和解释器的编程人员特别设计的工具: Flex 用于词法分析(lexical analysis,或称 scanning),把输入分割成一个个有意义的词块,称为记号(t ...

  8. 练习-用if语句替换三元运算符和选择结构-标准的switch语句

    if语句和三元运算符的互换 在某些简单的应用中,if语句是可以和三元运算符互换使用的 public static void main(String[] args) { int a = 10; int ...

  9. 【Python3】列表字典集合元组

    1 列表 1.1 定义与索引 在Python中,第一个列表元素的下标为 0通过将索引指定为 -1 可以让Python返回最后一个列表元素 inventory = ['sword', 'armor', ...

  10. 【新人福利】使用CSDN 官方插件,赠永久免站内广告特权 >>电脑端访问:https://t.csdnimg.cn/PVqS

    [新人福利]使用CSDN 官方插件,赠永久免站内广告特权 >>电脑端访问:CSDN开发助手 [新人福利]使用CSDN 官方插件,赠永久免站内广告特权 >>电脑端访问:https ...