题目
思路
可恶,拿到这道题,还真的被唬住了……
我一开始转化成了这样的模型:有 q q q 个带宽为 3 3 3 的带状矩阵,每次询问一个区间内的矩阵的乘积的某一位,且保证询问按照左端点排序后,右端点单调不降。
由于矩阵可能不满秩,不能使用前缀积。那么考虑类似 R M Q \rm{RMQ} RMQ 的做法,很快会口胡出来一个:每 m m m 个矩阵作为一个块,对于这 q m q\over m mq 个块的序列, O ( q n 3 m − 1 ) \mathcal O(qn^3m^{-1}) O(qn3m−1) 建立线段树,每次询问需要 log q \log q logq 次矩阵乘法;而散块的部分,利用带状矩阵的特点,可以 O ( m n 2 ) \mathcal O(mn^2) O(mn2) 直接乘。
询问时的矩阵乘法,可以优化到
O
(
n
2
)
\mathcal O(n^2)
O(n2),因为只询问最终结果矩阵的某一个位置,每次的乘积只保留那一行。那么总复杂度就是
O
(
q
n
3
m
−
1
+
q
n
2
m
+
q
n
2
log
q
)
\mathcal O(qn^3m^{-1}+qn^2m+qn^2\log q)
O(qn3m−1+qn2m+qn2logq),平衡一下取
m
=
n
m=\sqrt{n}
m=n 时复杂度为
O
(
q
n
2.5
+
q
n
2
log
q
)
\mathcal O(qn^{2.5}+qn^2\log q)
O(qn2.5+qn2logq)
此题中
n
⩽
log
q
\sqrt{n}\leqslant\log q
n⩽logq,所以其实就是比标算多了一个
log
q
\log q
logq,不过线段树的常数行不行啊。
2021 / 9 / 14 u p d a t e \tt 2021/9/14\;update 2021/9/14update:直接猫树(分治)就可以做到 q n 2 log q qn^2\log q qn2logq 了……为什么要写前面这些复杂的东西啊……
发现上面的做法的问题是:并没有用上 “只在上面删除、下面加入” 的约束条件。本来这东西应该是一个 队列 才对。
如果我们是栈的操作,就很简单了。飞快地联想起双端栈!只要一想起这玩意儿,整道题就已经做完了。
时间复杂度 O ( q n 2 ) \mathcal O(qn^2) O(qn2),大概是理论最优吧……
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(int x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) writeint(x/10);
putchar((x-x/10*10)^48);
}
const int MaxN = 20;
const int MaxQ = 300005;
const int Mod = 1e9+7;
int dp[MaxQ<<1][MaxN][MaxN], n;
char maze[MaxQ<<1][MaxN+2], opt[9];
void trans(int from,int to){
rep(i,0,n-1) rep(j,0,n-1){
dp[to][i][j] = 0;
rep(k,max(0,i-1),i+1)
if(k < n && maze[to][i] == maze[from][k]){
dp[to][i][j] += dp[from][k][j];
dp[to][i][j] %= Mod;
}
}
}
int main(){
n = readint();
int q = readint();
int fro = MaxQ, bac = fro-1;
for(int ans,a,b; q; --q){
scanf("%s",opt);
if(*opt == 'a'){
scanf("%s",maze[++bac]);
if(bac == MaxQ)
rep(i,0,n-1) rep(j,0,n-1)
dp[bac][i][j] = (i == j);
else trans(bac-1,bac);
}
else if(*opt == 'r'){
if(fro == MaxQ){
int d = bac+1-MaxQ;
rep(i,fro=MaxQ-d,MaxQ-1)
memcpy(maze[i],maze[i+d],n);
rep(i,0,n-1) rep(j,0,n-1)
dp[MaxQ-1][i][j] = (i == j);
drep(p,MaxQ-2,fro) trans(p+1,p);
bac = MaxQ-1; // clear
}
++ fro; // pop_front()
}
else if(*opt == 'p'){
a = readint()-1, b = readint()-1;
if(fro == MaxQ || bac == MaxQ-1)
rep(i,ans=0,n-1) ans = (ans+1ll*
dp[fro][a][i]*dp[bac][b][i])%Mod;
else rep(i,ans=0,n-1) rep(j,max(0,i-1),i+1)
if(j < n && maze[MaxQ-1][i] == maze[MaxQ][j])
ans = (ans+1ll*dp[fro][a][i]*dp[bac][b][j])%Mod;
printf("%d\n",ans);
}
}
return 0;
}
后记
记得
l
l
s
w
\sf llsw
llsw 上课的时候还给我说:滑动窗口求任意区间最小值,只需要用这个双端栈,两边维护
S
T
\rm ST
ST 表就行。然后我对着这道题想了一个小时。