小猫吃火龙果

link

考虑用分块维护。由于有交换两种字母的操作,那么对于每个就要维护三个字母所有置换( 6 6 6 种)。将 A B C , A C B , B A C , B C A , C A B , C B A ABC,ACB,BAC,BCA,CAB,CBA ABC,ACB,BAC,BCA,CAB,CBA 分别表示为 0 , 1 , 2 , 3 , 4 , 5 0,1,2,3,4,5 0,1,2,3,4,5;将修改操作交换 A B , A C , B C AB,AC,BC AB,AC,BC 分别表示为 0 , 1 , 2 0,1,2 0,1,2;记块长为 B B B


c x , y c_{x,y} cx,y 表示字母是 y y y,经过置换 x x x 会变成的字母。
b x , y b_{x,y} bx,y 表示经过修改 y y y 后,置换 x x x 会变成的置换。
t o i d , x , y to_{id,x,y} toid,x,y 表示第 i d id id 个块置换为 x x x,开始是字母 y y y,走完这个块后所变成的字母。
t i d t_{id} tid 表示第 i d id id 个块的置换。

显然 c , b c,b c,b 可以打表, t o to to 可以 O ( B ) O(B) O(B) 求出。

下面考虑维护。

  • 修改。对于散块先更新所在的块的真实值,然后直接修改,再 O ( B ) O(B) O(B) 求出 t o i d to_{id} toid;对于整块就用 b b b 更改 t i d t_{id} tid 即可。
  • 查询。对于散块就直接暴力比对;对于整块就用之前求出的 t o to to 更新答案。

时间复杂度 O ( n n ) O(n\sqrt n) O(nn ),但是由于修改时对于散块的求 t o to to 的时间复杂度实际上要带上 18 18 18 的常数,所以块长取 ⌈ n 18 ⌉ \sqrt{\lceil\frac n{18}\rceil} 18n 比较合适。

具体实现参照代码:

#include<bits/stdc++.h>
#define getnxt(now,x) (now==(x+1)%3?x:now)
using namespace std;
const int N=2e5+1;
int n,m,block,to[2000][6][3],t[2000];
int c[6][3]={{0,1,2},{0,2,1},{1,0,2},{1,2,0},{2,0,1},{2,1,0}};
int b[6][3]={{2,5,1},{3,4,0},{0,3,4},{1,2,5},{5,1,2},{4,0,3}};
char a[N];
void renew(int id)
{
    int l=id*block+1,r=min(id*block+block,n);
    for(int i=l;i<=r;i++) a[i]=c[t[id]][a[i]-65]+65;
    t[id]=0;
}
void update(int id)
{
    int l=id*block+1,r=min(id*block+block,n);
    for(int x=0;x<6;x++){
        for(int y=0;y<3;y++){
            to[id][x][y]=y;
            for(int i=l;i<=r;i++){
                to[id][x][y]=getnxt(to[id][x][y],c[x][a[i]-65]);
            }
        }
    }
}
int main()
{
    freopen("training.in","r",stdin);
    freopen("training.out","w",stdout);
    cin.tie(0)->sync_with_stdio(0);
    cin>>n>>m>>(a+1);
    block=sqrt((n+17)/18);
    for(int i=0;i<=(n+block-1)/block;i++) update(i);
    for(int i=1,op,l,r;i<=m;i++){
        char x,y;
        cin>>op>>l>>r>>x;
        if(op){
            int minr=min((l-1)/block*block+block,r),id=(l-1)/block;
            x-=65;
            while(l<=minr){
                x=getnxt(x,c[t[id]][a[l]-65]);
                l++;
            }
            while(r-l+1>=block){
                id=(l-1)/block;
                x=to[id][t[id]][x];
                l+=block;
            }
            id=(l-1)/block;
            while(l<=r){
                x=getnxt(x,c[t[id]][a[l]-65]);
                l++;
            }
            cout<<char(x+65)<<"\n";
        }
        else{
            cin>>y;
            if(x==y) continue;
            if(x>y) swap(x,y);
            int type=(x=='A'?y=='B'?0:1:2);
            int minr=min((l-1)/block*block+block,r),id=(l-1)/block;
            renew(id);
            while(l<=minr){
                if(a[l]==x) a[l]=y;
                else if(a[l]==y) a[l]=x;
                l++;
            }
            update(id);
            while(r-l+1>=block){
                t[(l-1)/block]=b[t[(l-1)/block]][type];
                l+=block;
            }
            renew(id=(l-1)/block);
            while(l<=r){
                if(a[l]==x) a[l]=y;
                else if(a[l]==y) a[l]=x;
                l++;
            }
            update(id);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值