题面
题解
直接的想法是矩阵加速 DP,记录两行的 DP 状态,用一个大小为 \(2n\times 2n\) 的矩阵记录,每次相当于询问一段区间的矩阵乘积,如果使用线段树维护的话是 \(O(n^3q\log q)\) 的。
使用优化可以做到 \(O(n^3q+n^2q\log q)\),下文算法中的优化方法与此优化方法类似,先略去。
注意到询问的 \(l,r\) 是单调的,有一个 trick:
-
对于一类问题:在序列尾部加入、首部删除,信息不具有可减性,询问当前序列时,不妨维护两个栈,分别代表 \([l,m]\) 和 \([m+1,r]\),其中第一个栈维护 \([l,m]\) 每个后缀的答案(顺便也维护了整体答案),第二个栈维护 \([m+1,r]\) 整体的答案。
加入时(\(r\) 增大)在第二个栈中插入,并更新第二个栈整体的答案。
删除时(\(l\) 增大)若第一个栈非空,直接删除第一个栈的首元素并利用记录的后缀信息更新第一个栈整体的答案,否则将第二个栈复制到第一个栈并清空第二个栈,然后暴力计算存储第一个栈每一个后缀的答案。
询问时合并第一个栈整体答案和第二个栈整体答案即可。
复杂度降至 \(O(n^3q)\)。
接下来是优化矩阵乘法:
-
更新第二个栈整体的答案、暴力计算第一个栈每一个后缀的答案:这两步中的矩乘都是一个矩阵乘上一个稀疏矩阵(只有 \(O(n)\) 个元素),可以加速到 \(O(n^2)\)。
-
询问时合并第一个栈整体答案和第二个栈整体答案:由于我们已经知道了初始矩阵(一个列向量),我们直接用这个列向量先乘第一个矩阵得到一个新的列向量,再用这个新的列向量乘第二个矩阵即可。列向量乘矩阵也是 \(O(n^2)\) 的。
上面线段树做法中提及的优化就和这里类似。
复杂度降至 \(O(n^2q)\)。
实际上可以不需要特殊考虑第二种特殊情况,因为列向量其实也是稀疏矩阵。
常数和空间有一点点大(因为矩阵大小是 \(2n\),所以会乘 \(4\) 倍)。
#include<bits/stdc++.h>
#define N 100010
#define re register
using namespace std;
namespace modular
{
const int mod=998244353;
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
inline void Add(int &x,int y){x=x+y>=mod?x+y-mod:x+y;}
}using namespace modular;
inline int read()
{
int 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<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
const int fx[]={1,2,2,1},fy[]={-2,-1,1,2};
int n,nn,q;
char s[N][21];
struct Matrix
{
int a[41][41];
Matrix(bool opt=0)
{
for(int i=1;i<=nn;i++)
for(int j=1;j<=nn;j++)
a[i][j]=(i==j?opt:0);
}
}b[N];
Matrix mula(const Matrix &a,const Matrix &b)
{
Matrix c(0);
for(re int i=1;i<=nn;i++)
{
for(re int j=1;j<=nn;j++)
{
if(!a.a[i][j]) continue;
for(re int k=1;k<=nn;k++)
Add(c.a[i][k],mul(a.a[i][j],b.a[j][k]));
}
}
return c;
}
Matrix mulb(const Matrix &a,const Matrix &b)
{
Matrix c(0);
for(re int i=1;i<=nn;i++)
{
for(re int j=1;j<=nn;j++)
{
if(!b.a[i][j]) continue;
for(re int k=1;k<=nn;k++)
Add(c.a[k][j],mul(a.a[k][i],b.a[i][j]));
}
}
return c;
}
int top1,top2;
int sta2[N];
Matrix sum1[N>>1],sum2;
inline void pop()
{
if(top1) top1--;
else
{
assert(top2);
sum1[0]=sum2=Matrix(1);
while(top2)
{
top1++;
sum1[top1]=mulb(sum1[top1-1],b[sta2[top2]]);
top2--;
}
top1--;
}
}
inline void insert(const int id)
{
sta2[++top2]=id;
sum2=mula(b[id],sum2);
}
int main()
{
n=read(),nn=n<<1,q=read();
sum1[0]=sum2=Matrix(1);
int nl=1,nr=0;
while(q--)
{
char opt[4];
scanf("%s",opt);
if(opt[0]=='A')
{
++nr;
scanf("%s",s[nr]+1);
for(int i=1;i<=n;i++)
{
if(s[nr][i]=='#') continue;
for(int j=0;j<4;j++)
{
int xx=fx[j]-1,yy=i+fy[j];
if(yy<1||yy>n) continue;
b[nr].a[i][xx*n+yy]=1;
}
}
for(int i=1;i<=n;i++)
b[nr].a[n+i][i]=1;
if(nr>nl) insert(nr);
}
if(opt[0]=='D')
{
if(nr>nl) pop();
++nl;
}
if(opt[0]=='Q')
{
int u=read(),v=read();
if(nr<nl)
{
puts("0");
continue;
}
if(s[nl][u]=='#')
{
puts("0");
continue;
}
Matrix res(0);
res.a[u][1]=1;
res=mulb(sum1[top1],res);
for(int i=1;i<=nn;i++)
for(int j=2;j<=nn;j++)
res.a[i][j]=0;
res=mulb(sum2,res);
printf("%d\n",res.a[v][1]);
}
}
return 0;
}
/*
5 10
Add .....
Add .....
Que 3 1
*/
标签:ch,int,矩阵,top1,trick,第二个,XXSY,答案,乘法
From: https://www.cnblogs.com/ez-lcw/p/16842990.html