【学习笔记】[NOI2021] 密码箱

想起之前考试做过的一道题,当时做法好像是用线段树维护一个分段复合函数,当时切掉了,但是这道题换成矩阵就又不会了。还是太菜了。

既然都要复合了,肯定要满足结合律。因为满足了结合律,所以线段树上左右儿子可以合并。

什么东西满足结合律?自然是矩阵。这个东西用来处理线性变换尤其好用,而且矩阵的性质其实是相当丰富的,比如矩阵可以求逆。

方便起见,就用左乘好了。分数只有分子和分母两项,所以矩阵的大小为 2 × 2 2\times 2 2×2即可。那么我们有 [ x   y ] × [ a i   1 1    0 ] = [ a i x + y    x ] \begin{bmatrix}x\ y\end{bmatrix}\times \begin{bmatrix}a_i\ 1\\1\ \ 0\end{bmatrix}=\begin{bmatrix}a_ix+y\ \ x\end{bmatrix} [x y]×[ai 11  0]=[aix+y  x]。大胆猜测 W W W, E E E两种操作都分别对应一种矩阵,具体来说:

1.1 1.1 1.1 W W W操作对应左乘矩阵 [ 1   1 0   1 ] \begin{bmatrix}1\ 1\\0\ 1\end{bmatrix} [1 10 1]
1.2 1.2 1.2 E E E 操作对应两种情况,但是有一个结论:两种情况对应同一个矩阵。第二种情况对应的矩阵是 [ 1   1 1   0 ] × [ 1   1 1   0 ] × [ 1   − 1 0      1 ] = [ 2   − 1 1      0 ] \begin{bmatrix}1\ 1\\1 \ 0\end{bmatrix}\times \begin{bmatrix}1\ 1\\1\ 0\end{bmatrix}\times \begin{bmatrix}1\ -1\\0\ \ \ \ 1\end{bmatrix}=\begin{bmatrix}2\ -1\\1\ \ \ \ 0\end{bmatrix} [1 11 0]×[1 11 0]×[1 10    1]=[2 11    0];第一种情况对应的矩阵是 [ 1   1 1   0 ] × [ 1   1 0   1 ] × [ 1   1 1   0 ] − 1 = [ 2   − 1 1      0 ] \begin{bmatrix}1\ 1\\1\ 0\end{bmatrix}\times \begin{bmatrix}1\ 1\\0\ 1\end{bmatrix}\times \begin{bmatrix}1\ 1\\1\ 0\end{bmatrix}^{-1}=\begin{bmatrix}2\ -1\\1\ \ \ \ 0\end{bmatrix} [1 11 0]×[1 10 1]×[1 11 0]1=[2 11    0]。不难发现这些巧合事实上都是出题人的精心设计。

然后把线段树换成平衡树,这道题就做完了。

如果正常来做的话,那么一个节点就要维护 4 4 4个矩阵,这样常数就会有点大。但是因为两个标记的先后顺序不影响答案所以代码难度也还好。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

写了一发,感受是甚至比普通的平衡树要好写。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
const int N=2e5+5;
struct Matrix{
    ll c[2][2];
    Matrix(){memset(c,0,sizeof c);}
    Matrix operator *(const Matrix &a)const{
        Matrix r;
        for(int k=0;k<2;k++){
            for(int i=0;i<2;i++){
                for(int j=0;j<2;j++){
                    r.c[i][j]=(r.c[i][j]+c[i][k]*a.c[k][j]%mod)%mod;
                }
            }
        }
        return r;
    }
}W,E,I;
struct node{
    Matrix A,B,Av,Bv;
    Matrix v,rv;
    int tagflip,tagrev;
    int l,r,siz,fix;
}t[N];
int n,Q,rt;
string str;
int gcd(int x,int y){
    return y==0?x:gcd(y,x%y);
}
void getans(){
    ll x=t[rt].A.c[0][0],y=(t[rt].A.c[0][0]+t[rt].A.c[0][1])%mod;
    x=(x+mod)%mod,y=(y+mod)%mod;
    cout<<x<<" "<<y<<"\n";
}
int newnode(char ch){
    n++;t[n].siz=1,t[n].fix=rand();
    if(ch=='E'){
        t[n].A=t[n].Av=t[n].v=E;
        t[n].B=t[n].Bv=t[n].rv=W;
    }
    else{
        t[n].A=t[n].Av=t[n].v=W;
        t[n].B=t[n].Bv=t[n].rv=E;
    }
    return n;
}
//fixed
void pushup(int p){
    int l=t[p].l,r=t[p].r;
    t[p].siz=t[l].siz+t[r].siz+1;
    t[p].A=t[r].A*t[p].v*t[l].A;
    t[p].Av=t[l].Av*t[p].v*t[r].Av;
    t[p].B=t[r].B*t[p].rv*t[l].B;
    t[p].Bv=t[l].Bv*t[p].rv*t[r].Bv;
}
//fixed
void flip(int p){
    t[p].tagflip^=1;
    swap(t[p].A,t[p].B),swap(t[p].Av,t[p].Bv),swap(t[p].v,t[p].rv);
}
void reverse(int p){
    t[p].tagrev^=1,swap(t[p].l,t[p].r);
    swap(t[p].A,t[p].Av),swap(t[p].B,t[p].Bv);
}
void pushdown(int p){
    int l=t[p].l,r=t[p].r;
    if(t[p].tagflip){
        if(l)flip(l);
        if(r)flip(r);
        t[p].tagflip=0;
    }
    if(t[p].tagrev){
        if(l)reverse(l);
        if(r)reverse(r);
        t[p].tagrev=0;
    }
}
//fixed
int merge(int x,int y){
    if(!x||!y)return x+y;
    if(t[x].fix>t[y].fix){
        pushdown(x);
        t[x].r=merge(t[x].r,y);
        pushup(x);
        return x;
    }
    else{
        pushdown(y);
        t[y].l=merge(x,t[y].l);
        pushup(y);
        return y;
    }
}
void split(int rt,int val,int &x,int &y){
    if(!rt){
        x=y=0;
        return;
    }
    pushdown(rt);
    int l=t[rt].l,r=t[rt].r;
    if(t[l].siz>=val){
        y=rt;
        split(l,val,x,t[y].l);
        pushup(y);
    }
    else{
        x=rt;
        split(r,val-t[l].siz-1,t[x].r,y);
        pushup(x);
    }
}
void build(int &p,int l,int r){
    if(l>r)return;
    int mid=l+r>>1;p=mid;
    t[p].fix=rand();
    if(str[p-1]=='E'){
        t[p].v=E;
        t[p].rv=W;
    }
    else{
        t[p].v=W;
        t[p].rv=E;
    }
    build(t[p].l,l,mid-1);
    build(t[p].r,mid+1,r);
    pushup(p);
}
void Flip(int l,int r){
    int x,y,z;
    split(rt,l-1,x,y);
    split(y,r-l+1,y,z);
    flip(y);rt=merge(merge(x,y),z);
}
void Reverse(int l,int r){
    int x,y,z;
    split(rt,l-1,x,y);
    split(y,r-l+1,y,z);
    reverse(y);rt=merge(merge(x,y),z);
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>Q>>str;srand(time(0));
    W.c[0][0]=1,W.c[0][1]=1,W.c[1][0]=0,W.c[1][1]=1;
    E.c[0][0]=2,E.c[0][1]=-1,E.c[1][0]=1,E.c[1][1]=0;
    I.c[0][0]=I.c[1][1]=1;
    t[0].A=t[0].B=t[0].Av=t[0].Bv=I;
    build(rt,1,n);getans();
    for(int i=1;i<=Q;i++){
        cin>>str;
        if(str[0]=='A'){
            char ch;cin>>ch;
            rt=merge(rt,newnode(ch));
        }
        else if(str[0]=='F'){
            int l,r;cin>>l>>r;
            Flip(l,r);
        }
        else if(str[0]=='R'){
            int l,r;cin>>l>>r;
            Reverse(l,r);
        }
        getans();
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值