给定了只含字符A B的字符串,操作1是区间中的A变成B,B变成A,操作2是区间执行上述操作,输入整数(A,B)
输出结果(A,B)
。
观察发现,A=A+B,B=A+B这种线性方程可以转化成矩阵乘法,(可以类比斐波那契数列的递推公式),而矩阵乘法是有结合律的,可以用线段树维护。
于是试着写成矩阵乘法形式:(我们这里写成右乘的形式,为什么不写成左乘呢?后面会解释。)
[ x , y ] ∗ [ 1 , 0 1 , 1 ] = [ x + y , y ] \left [ x,y \right ] * \begin{bmatrix}1,0\\1,1\end{bmatrix} = \left [x+y,y\right] [x,y]∗[1,01,1]=[x+y,y] (记这个递推矩阵为矩阵1)
[ x , y ] ∗ [ 1 , 1 0 , 1 ] = [ x , x + y ] \left [ x,y \right ] * \begin{bmatrix}1,1\\0,1\end{bmatrix} = \left [x,x+y\right] [x,y]∗[1,10,1]=[x,x+y](记这个递推矩阵为矩阵2)
也就是说,遇到A相当于 [ x , y ] \left [ x,y \right ] [x,y]右乘第一个矩阵,遇到B相当于它右乘第二个矩阵。
操作1相当于把矩阵1变成矩阵2,矩阵2变成矩阵1
我们可以用线段树对每个位置维护两个矩阵乘积,一个是初始的字符串状态下的乘积,一个是进行了操作1情况下的乘积,每次区间修改就是这俩乘积swap一下。
#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
const int maxn = 1e5 + 10;
const ll mod = 998244353;
const ll inf = (ll)4e17+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){
if(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';ch=getchar();}
return x*f;
}
//给定只含AB的字符串,区间修改:A改成B B改成A,
//区间查询:给定x y,L,R,从L往右扫 遇到A:x=x+y 遇到B y=x+y 输出最终的x y
//A B可以当成两种矩阵 线段树维护矩阵乘积(利用结合律) 翻转操作可以维护两个线段树
struct mat
{
ll a[2][2];
void initA()//遇到字符A
{
a[0][0]=1,a[0][1]=0;
a[1][0]=1,a[1][1]=1;
}
void initB()//遇到字符B
{
a[0][0]=1,a[0][1]=1;
a[1][0]=0,a[1][1]=1;
}
mat operator * (const mat&f)const
{
mat ans;
for(int i=0;i<2;i++)
{
for(int j=0;j<2;j++)
{
ans.a[i][j]=0;
for(int k=0;k<2;k++)
{
ans.a[i][j]=(ans.a[i][j]+a[i][k]*f.a[k][j]%INF)%INF;
}
}
}
return ans;
}
};
struct node
{
#define lc rt<<1
#define rc rt<<1|1
mat tr[2];//0是真实的 1是翻转后的乘积
bool tag;
int l,r;
}tree[maxn<<2];
char s[maxn];
inline void pushup(int rt)
{
tree[rt].tr[0]=tree[lc].tr[0]*tree[rc].tr[0];
tree[rt].tr[1]=tree[lc].tr[1]*tree[rc].tr[1];
}
inline void build(int rt,int l,int r)
{
tree[rt].l=l,tree[rt].r=r;
if(l==r)
{
if(s[l]=='A')//0表示真实情况下的乘积 1代表翻转的结果
tree[rt].tr[0].initA(),tree[rt].tr[1].initB();
else
tree[rt].tr[0].initB(),tree[rt].tr[1].initA();
return ;
}
int mid=l+r>>1;
build(lc,l,mid);build(rc,mid+1,r);
pushup(rt);
}
inline void change(int rt) //区间翻转 两个乘积交换
{
swap(tree[rt].tr[0],tree[rt].tr[1]);
tree[rt].tag ^= 1;
}
inline void pushdown(int rt)
{
if(tree[rt].tag)
{
change(lc);
change(rc);
tree[rt].tag=0;
}
}
inline void upd(int rt,int vl,int vr)
{
int l=tree[rt].l,r=tree[rt].r;
if(r<vl || l>vr) return ;
if(vl<=l && r<=vr)
{
change(rt);
return ;
}
pushdown(rt);
upd(lc,vl,vr);
upd(rc,vl,vr);
pushup(rt);
}
inline mat qry(int rt,int vl,int vr)
{
int l=tree[rt].l,r=tree[rt].r;
if(vl<=l && r<=vr) return tree[rt].tr[0];//0代表真实情况下的乘积
pushdown(rt);
int mid=l+r>>1;
if(vr<=mid) return qry(lc,vl,vr);
else if(vl>mid) return qry(rc,vl,vr);
return qry(lc,vl,vr)*qry(rc,vl,vr);
}
int n,q;
int main()
{
scanf("%d %d",&n,&q);
scanf("%s",s+1);
build(1,1,n);
while(q--)
{
int f=read(),l=read(),r=read();
ll x,y;
if(f==1)
{
upd(1,l,r);
}
else
{
x=read(),y=read();
mat t=qry(1,l,r);
ll ans1=t.a[0][0]*x%INF+t.a[1][0]*y%INF;
ll ans2=x*t.a[0][1]%INF+y*t.a[1][1]%INF;
printf("%lld %lld\n",ans1%INF,ans2%INF);
}
}
return 0;
}
本题涉及的TRICK:
①线性递推式转化成矩阵乘积
②利用矩阵的结合律,用线段树来维护矩阵乘积 。(这里特别要注意,矩阵乘积没有交换律,就是从左往右算(即:具有从左往右的方向性),而线段树的区间合并也是具有方向性的,也是从左往右,所以这里需要对应起来。回到本题,我们操作2写成矩阵乘法都是在右乘,所以方向和线段树是一致的。)
如果我们把矩阵乘法写成左乘的形式可做吗, 答案是可以的:
[ 1 , 1 0 , 1 ] ∗ [ x y ] = [ x + y y ] \begin{bmatrix}1,1\\0,1\end{bmatrix} * \begin{bmatrix} x\\y\end{bmatrix} =\begin{bmatrix} x+y\\y\end{bmatrix} [1,10,1]∗[xy]=[x+yy]
[ 1 , 0 1 , 1 ] ∗ [ x y ] = [ x x + y ] \begin{bmatrix}1,0\\1,1\end{bmatrix} * \begin{bmatrix} x\\y\end{bmatrix} =\begin{bmatrix} x\\x+y\end{bmatrix} [1,01,1]∗[xy]=[xx+y]
遇到A/B就让 [ x y ] \begin{bmatrix} x\\y\end{bmatrix} [xy]左乘相应矩阵,最终也是得到
[ x y ] \begin{bmatrix} x\\y\end{bmatrix} [xy]左边乘了一串矩阵的形式,但是这个矩阵乘式是从左往右计算的,所以第一个字符对应的矩阵应该是在计算的最后一个位置,而线段树的方向也是从左往右的,所以也是线段树维护的区间的最后一个位置。
简洁而言:第i个字符对应矩阵应该放在线段树的n+1-i位置。
代码:
#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
const int maxn = 1e5 + 10;
const ll mod = 998244353;
const ll inf = (ll)4e17+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){
if(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';ch=getchar();}
return x*f;
}
//给定只含AB的字符串,区间修改:A改成B B改成A,
//区间查询:给定x y,L,R,从L往右扫 遇到A:x=x+y 遇到B y=x+y 输出最终的x y
//A B可以当成两种矩阵 线段树维护矩阵乘积(利用结合律) 翻转操作可以维护两个线段树
struct mat
{
ll a[2][2];
void initA()//注意这里的矩阵变了,因为改成了左乘
{
a[0][0]=1,a[0][1]=1;
a[1][0]=0,a[1][1]=1;
}
void initB()
{
a[0][0]=1,a[0][1]=0;
a[1][0]=1,a[1][1]=1;
}
mat operator * (const mat&f)const
{
mat ans;
for(int i=0;i<2;i++)
{
for(int j=0;j<2;j++)
{
ans.a[i][j]=0;
for(int k=0;k<2;k++)
{
ans.a[i][j]=(ans.a[i][j]+a[i][k]*f.a[k][j]%INF)%INF;
}
}
}
return ans;
}
};
struct node
{
#define lc rt<<1
#define rc rt<<1|1
mat tr[2];//0是真实的 1是翻转后的乘积
bool tag;
int l,r;
}tree[maxn<<2];
char s[maxn];
int n,q;
inline void pushup(int rt)
{
tree[rt].tr[0]=tree[lc].tr[0]*tree[rc].tr[0];
tree[rt].tr[1]=tree[lc].tr[1]*tree[rc].tr[1];
}
inline void build(int rt,int l,int r)
{
tree[rt].l=l,tree[rt].r=r;
if(l==r)
{
if(s[n-l+1]=='A')//l对应的字符是n-l+1
tree[rt].tr[0].initA(),tree[rt].tr[1].initB();
else
tree[rt].tr[0].initB(),tree[rt].tr[1].initA();
return ;
}
int mid=l+r>>1;
build(lc,l,mid);build(rc,mid+1,r);
pushup(rt);
}
inline void change(int rt) //区间翻转 代表两个矩阵的交换
{
swap(tree[rt].tr[0],tree[rt].tr[1]);
tree[rt].tag ^= 1;
}
inline void pushdown(int rt)
{
if(tree[rt].tag)
{
change(lc);
change(rc);
tree[rt].tag=0;
}
}
inline void upd(int rt,int vl,int vr)
{
int l=tree[rt].l,r=tree[rt].r;
if(r<vl || l>vr) return ;
if(vl<=l && r<=vr)
{
change(rt);
return ;
}
pushdown(rt);
upd(lc,vl,vr);
upd(rc,vl,vr);
pushup(rt);
}
inline mat qry(int rt,int vl,int vr)
{
int l=tree[rt].l,r=tree[rt].r;
if(vl<=l && r<=vr) return tree[rt].tr[0];
pushdown(rt);
int mid=l+r>>1;
if(vr<=mid) return qry(lc,vl,vr);
else if(vl>mid) return qry(rc,vl,vr);
return qry(lc,vl,vr)*qry(rc,vl,vr);
}
inline mat qry1(int rt,int vl,int vr)
{
int l=tree[rt].l,r=tree[rt].r;
if(vl<=l && r<=vr)
{
return tree[rt].tr[1];
}
pushdown(rt);
int mid=l+r>>1;
if(vr<=mid) return qry1(lc,vl,vr);
else if(vl>mid) return qry1(rc,vl,vr);
return qry1(lc,vl,vr)*qry1(rc,vl,vr);
}
int main()
{
scanf("%d %d",&n,&q);
scanf("%s",s+1);
build(1,1,n);
while(q--)
{
int f=read(),l=read(),r=read();
ll x,y;
l=n+1-l;//
r=n+1-r;//同理 区间需要对称一下
if(l>r) swap(l,r);
if(f==1)
{
upd(1,l,r);
}
else
{
x=read(),y=read();
mat t=qry(1,l,r);
ll ans1=t.a[0][0]*x%INF+t.a[0][1]*y%INF;
ll ans2=x*t.a[1][0]%INF+y*t.a[1][1]%INF;
printf("%lld %lld\n",ans1%INF,ans2%INF);
}
}
return 0;
}