[NOI2021]密码箱
老师留的几道题里第一个做了的,写篇题解纪念一下。
这道题主要想要写一下我做题的思路。
题目链接:luoguP7739 。
首先我们可以注意到这道题不同寻常的地方:别的题目都是让我们维护序列,但是这道题却让我们维护操作序列。
似乎非常麻烦啊。
但是什么东西可以维护呢?矩阵!
再看一下题目中的操作:翻转、反转。
一眼就可以看出正解是平衡树维护矩阵。
这些思路大概是我读完题之后就知道的,比较明显。
知道了算法,其实我们只需要推出这些矩阵就可以了,这才是这道题的重点。
我们现在再来想一个问题:假如我们现在通过矩阵,维护了某一个序列的答案。
我们现在给这个序列后面添加一个数字,我们可以算出答案吗?
似乎不行,或者说起码没有特别简单的方法。
但是如果这个数字添加在了整个序列的前面,我们却很好维护。
假如说 $ f(a_0,a_1,\dots,a_k) $ 的答案为 $ \dfrac{x}{y} $ ,这个时候序列变成 $ (c,a_0,a_1,\dots,a_k) $ ,答案显然会变成 $ c+\dfrac{y}{x}=\dfrac{cx+y}{x} $ 。
那也就是:
所以我们得到了一些启发:序列倒着会比较好维护。
其实正着可能也差不多,但是我还是觉得倒着比较方便。
然后我们再来看题目中给的操作。
首先我们来看 W 操作。
将序列中最后一个值加一,因为我们倒着维护序列,所以就是整个序列第一个位置加一。
假设说这个地方原来的值是 $ c $ ,那么这个地方对应的矩阵就是 $ \begin{bmatrix} c&1 \ 1&0\end{bmatrix} $ 。
我们现在要将这个地方的矩阵要变成 $ \begin{bmatrix} c+1&1 \ 1&0\end{bmatrix} $ 。
因为对于任何 $ c $ ,这个变换都要成立,所以稍微手推一下,就可以发现:
接下来我们来推 E,你会发现似乎有点麻烦。不慌,我们直接推。
对于第一个数字不是 $ 1 $ 的情况,就是先把这个数字减一,然后在序列前面添上两个 $ 1 $ 。
所以我们先推一下如何给一个数字减一,感觉和上面差不多。
那么这个矩阵推出来就是:
然后再来推一下第一个数字是 $ 1 $ 的情况。不妨写一下原来的矩阵和变化后的矩阵。
假设原来第二个数是 $ c $ (第一个数是 $ 1 $ )。
那么开头的两个矩阵长这个样子:
我们将第二个数字加上一:
前两项都是常数项,我们把它们乘起来。
我们肯定尽量想将他表示成对第一个数字的操作,所以我们把第一项拆开:
可以带进去的数字有点多,不好算。
就这么卡住了嘛?
看看上面?
没错,E 操作直接用这一个矩阵就可以解决。
矩阵就这样推完了。
我们再来看题目中的这些操作。
操作一,直接在所有矩阵前添加一个数字。
操作三,直接整个区间翻转。
操作二,看起来不好搞,实际上和操作三一样,直接暴力维护没有转和转了之后这个区间长什么样子就行。
$ 4 $ 倍常数,建议实现的时候尽量常数写小一点。
代码:
#include<algorithm>
#include<cstring>
#include<random>
#include<cstdio>
#include<ctime>
using namespace std;
const int mod=998244353;
mt19937 _rand(time(0)^clock());
using ll=long long;
inline int read()
{
int s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
template<const int n,const int m>
struct mat
{
ll a[n][m];
template<const int q>
mat<n,q> operator * (mat<m,q>b)
{
mat<n,q>ans;
memset(ans.a,0,sizeof(ans.a));
for(int i=0;i<n;i++)
for(int k=0;k<m;k++)
for(int j=0;j<q;j++)
(ans.a[i][j]+=a[i][k]*b.a[k][j])%=mod;
return ans;
}
inline void debug()
{
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++) printf("%lld ",a[i][j]);
printf("\n");
}
printf("\n");
}
};
mat<2,2>num0[200001];
mat<2,2>num1[200001];
mat<2,2>num2[200001];
mat<2,2>num3[200001];
mat<2,2>val0[200001];
mat<2,2>val2[200001];
bool rev[200001];
bool fli[200001];
int pri[200001];
int siz[200001];
int ls[200001];
int rs[200001];
char in[100002];
int root;
int tot;
int n,m;
int Neww()
{
tot++;
pri[tot]=_rand(),siz[tot]=1;
num0[tot].a[0][0]=num0[tot].a[0][1]=num0[tot].a[1][1]=1;
num2[tot].a[0][0]=2,num2[tot].a[0][1]=-1,num2[tot].a[1][0]=1;
val0[tot]=num1[tot]=num0[tot],val2[tot]=num3[tot]=num2[tot];
return tot;
}
int Newe()
{
tot++;
pri[tot]=_rand(),siz[tot]=1;
num0[tot].a[0][0]=2,num0[tot].a[0][1]=-1,num0[tot].a[1][0]=1;
num2[tot].a[0][0]=num2[tot].a[0][1]=num2[tot].a[1][1]=1;
val0[tot]=num1[tot]=num0[tot],val2[tot]=num3[tot]=num2[tot];
return tot;
}
inline void push_down(int p)
{
if(rev[p])
{
rev[p]^=1;
rev[ls[p]]^=1,rev[rs[p]]^=1;
swap(ls[p],rs[p]);
swap(num0[p],num1[p]),swap(num2[p],num3[p]);
}
if(fli[p])
{
fli[p]^=1;
fli[ls[p]]^=1,fli[rs[p]]^=1;
swap(num0[p],num2[p]),swap(num1[p],num3[p]),swap(val0[p],val2[p]);
}
}
inline void push_up(int p)
{
if(ls[p]) push_down(ls[p]);
if(rs[p]) push_down(rs[p]);
num0[p]=num0[ls[p]]*val0[p]*num0[rs[p]];
num2[p]=num2[ls[p]]*val2[p]*num2[rs[p]];
num1[p]=num1[rs[p]]*val0[p]*num1[ls[p]];
num3[p]=num3[rs[p]]*val2[p]*num3[ls[p]];
siz[p]=siz[ls[p]]+siz[rs[p]]+1;
}
int merge(int u,int v)
{
// printf("merge %d %d\n",u,v);
if(!u||!v) return u|v;
if(pri[u]>=pri[v])
{
push_down(u);
rs[u]=merge(rs[u],v);
push_up(u);
return u;
}
else
{
push_down(v);
ls[v]=merge(u,ls[v]);
push_up(v);
return v;
}
}
void split(int p,int s,int &x,int &y)
{
if(!p){ x=y=0;return ;}
push_down(p);
if(siz[ls[p]]>=s)
{
y=p;
split(ls[p],s,x,ls[y]);
push_up(y);
}
else
{
x=p;
split(rs[p],s-siz[ls[p]]-1,rs[x],y);
push_up(x);
}
}
void print()
{
push_down(root);
printf("%lld %lld\n",(num0[root].a[0][0]+mod)%mod,(num0[root].a[0][0]+num0[root].a[0][1]+2*mod)%mod);
}
void debug()
{
// printf("root=%d\n",root);
// for(int i=1;i<=tot;i++)
// {
// printf("%d : ls=%d rs=%d rev=%d fli=%d siz=%d ",i,ls[i],rs[i],rev[i],fli[i],siz[i]);
// printf("%2lld %2lld %2lld %2lld %2lld %2lld %2lld %2lld %2lld %2lld %2lld %2lld\n",val0[i].a[0][0],val0[i].a[0][1],val2[i].a[0][0],val2[i].a[0][1],num0[i].a[0][0],num0[i].a[0][1],num1[i].a[0][0],num1[i].a[0][1],num2[i].a[0][0],num2[i].a[0][1],num3[i].a[0][0],num3[i].a[0][1]);
// printf(" ");
// printf("%2lld %2lld %2lld %2lld %2lld %2lld %2lld %2lld %2lld %2lld %2lld %2lld\n",val0[i].a[1][0],val0[i].a[1][1],val2[i].a[1][0],val2[i].a[1][1],num0[i].a[1][0],num0[i].a[1][1],num1[i].a[1][0],num1[i].a[1][1],num2[i].a[1][0],num2[i].a[1][1],num3[i].a[1][0],num3[i].a[1][1]);
// }
// printf("\n");
}
int main()
{
n=read(),m=read();scanf("%s",in+1);
num0[0].a[0][0]=num0[0].a[1][1]=1;
num1[0]=num2[0]=num3[0]=num0[0];
for(int i=1;i<=n;i++)
{
if(in[i]=='W') root=merge(Neww(),root);
else root=merge(Newe(),root);
}
print();
debug();
for(int i=1;i<=m;i++)
{
scanf("%s",in+1);
if(in[1]=='A')
{
scanf("%s",in+1);
if(in[1]=='E') root=merge(Newe(),root);
else root=merge(Neww(),root);
n++;
}
else if(in[1]=='R')
{
int r=n-read()+1,l=n-read()+1;
int x,y,z;
split(root,r,y,z);
split(y,l-1,x,y);
rev[y]^=1;
root=merge(merge(x,y),z);
}
else
{
int r=n-read()+1,l=n-read()+1;
int x,y,z;
split(root,r,y,z);
split(y,l-1,x,y);
// printf("l=%d r=%d x=%d y=%d z=%d\n",l,r,x,y,z);
debug();
fli[y]^=1;
root=merge(merge(x,y),z);
}
print();
debug();
}
return 0;
}
/*
2 2
WE
APPEND E
FLIP 1 2
*/
感谢观看!
本文来自博客园,作者:Wuyanru,转载请取得作者同意并注明原文链接:https://www.cnblogs.com/Wuyanru/p/NOI2021-password_box.html
本文内容如有侵权行为,请联系作者删除,qq:1395170470