为什么要写这个题的题解呢?
因为我觉得很可惜...尽管我做过类似的,类似这题 ,知道是线段树上分治跑dp,但是因为只做过一次,不敢写,因为怕bug然后调不出,事实上我又调了很久,确实菜,但是呢必须得说下次我绝对敢写.
这种题是基于线段树本身带有分治结构,对于每段来说就是先算段内贡献,然后算段外的贡献,假如你学过cdq分治就更好理解了.这是我当初学cdq分治做的视频 .
基于这些,我们令表示线段树管辖区间
,
到
连续的子串数量是多少.
然后统计技术即可,和普通线段树相比差别在于,把子树的信息整合到父亲节点.
然后并没有什么难点.
code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5,M=6;
const int mod=998244353;
map<char,int>id={{'F',1},{'e',2},{'i',3},{'M',4},{'a',5}};
char s[N];
struct SegTree{
int l,r;
char lazy;
ll f[M][M];//表示区间l,r i~j的子序列个数.
}Tr[N<<2];
void pushup(int u)
{
for(int i=1;i<=5;i++)
{
for(int j=i;j<=5;j++)
{
Tr[u].f[i][j]=(Tr[u<<1].f[i][j]+Tr[u<<1|1].f[i][j])%mod;
for(int k=i;k<j;k++)
{
Tr[u].f[i][j]=(Tr[u].f[i][j]+(Tr[u<<1].f[i][k]*Tr[u<<1|1].f[k+1][j])%mod)%mod;
}
}
}
}
void pushdown(int u)
{
if(Tr[u].lazy!=0)
{
memset(Tr[u<<1].f,0,sizeof Tr[u<<1].f);
Tr[u<<1].f[id[Tr[u].lazy]][id[Tr[u].lazy]]=(Tr[u<<1].r-Tr[u<<1].l+1);
Tr[u<<1].lazy=Tr[u].lazy;
memset(Tr[u<<1|1].f,0,sizeof Tr[u<<1|1].f);
Tr[u<<1|1].f[id[Tr[u].lazy]][id[Tr[u].lazy]]=(Tr[u<<1|1].r-Tr[u<<1|1].l+1);
Tr[u<<1|1].lazy=Tr[u].lazy;
Tr[u].lazy=0;
}
}
void modify(int u,int l,int r,char c)
{
if(l>Tr[u].r||r<Tr[u].l) return;
if(Tr[u].l>=l&&Tr[u].r<=r)
{
memset(Tr[u].f,0,sizeof Tr[u].f);
Tr[u].lazy=c;
Tr[u].f[id[c]][id[c]]=(Tr[u].r-Tr[u].l+1);
return;
}
pushdown(u);
modify(u<<1,l,r,c);
modify(u<<1|1,l,r,c);
pushup(u);
}
void build(int u,int l,int r)
{
Tr[u].l=l,Tr[u].r=r;Tr[u].lazy=0;
if(l==r)
{
memset(Tr[u].f,0,sizeof Tr[u].f);
Tr[u].f[id[s[l]]][id[s[l]]]=1;
return;
}
int mid=(l+r)>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
void run()
{
int n,q;
scanf("%d%d",&n,&q);
scanf("%s",s+1);
build(1,1,n);
while(q--)
{
int li,ri;
scanf("%d%d",&li,&ri);
char op;
cin>>op;
modify(1,li,ri,op);
printf("%lld\n",Tr[1].f[1][5]);
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
run();
}
return 0;
}

京公网安备 11010502036488号