题解 P7739 [NOI2021D2T2] 密码箱
最近正在学 FHQ-Treap 和高等代数,隐约记得ix35这题用平衡树做的,遂来挑战。
显然凭一己之力是做不出来的,于是瞄了一眼题解,让我十分震惊。但说真的,用矩阵来理解连分数算是Adhoc吧。
Problem
- 初始时数列长度为 \(2\) 且有 \(a_0 = 0, a_1 = 1\);
- 对数列依次进行若干次操作,其中每次操作是以下两种类型之一:
W类型:给数列的最后一项加 \(1\)。E类型:若数列的最后一项为 \(1\),则给倒数第二项加 \(1\);否则先给数列的最后一项减 \(1\),接着在数列尾再加两项,两项的值都是 \(1\)。
APPEND c,在现有操作序列后追加一次c类型操作,其中c为字符W或E。FLIP l r,反转现有操作序列中第 \(l\) 个至第 \(r\) 个(下标从 \(1\) 开始,修改包含端点 \(l\) 和 \(r\),下同)操作,即所有W变为E,所有E变为W。REVERSE l r,翻转现有操作序列中第 \(l\) 个至第 \(r\) 个操作,也就是将这个区间中的操作逆序。
若真实的密码为 \(\frac{a}{b}\),其中 \(a, b > 0\) 且 \(\gcd(a, b) = 1\),则你需要在对应的行内顺次输出 \(a\) 和 \(b\) 模 \(998244353\) 后的余数。
转化成矩阵问题
因为两者可以线性表示,我们可以得到
所以我们要求的东西就是
注意到 W 和 E 都是对末尾几位操作,我们可以考虑把两者都变成在最后右乘一个矩阵。
对于 W,我们要找
为此,我们要求一个\(A^{-1}B\)型的矩阵,可以写成增广矩阵 \((A|B)\) 并初等行变换得到\((I| A^{-1}B)\)
解得 \(W=\begin{bmatrix}1&0\\1&1\end{bmatrix}\)
对于 E,先看最后一项不是1的情况。仿照W的解法,我们得到
我们猜想另外一种情况也是如此,也就是我们希望
这是正确的。
因此,我们做到了,\(E=\begin{bmatrix}2&1\\-1&0\end{bmatrix}\)
处理区间操作
现在,问题转化成了:给定一个序列,和一种特殊的数W/E,满足结合律但不满足交换律。有三种操作:
- 在最后加一个W/E
- 把一段区间的W变E,E变W
- 把一段区间整个翻转
每做一步都要求出整个序列的和。
这下如果FHQ-Treap只记录一个子树的数之和,查询的时候总是需要dfs一遍,不合理。怎么办呢?
注意到,这两个区间操作都是对合,也就是做两次回到自己本身,这是很重要的性质!
我们需要保存什么?我们需要在查询的时候,能够不动子树也能给出子树的值被flip、reverse后的结果。
综合两者,我们发现:同时记录原来的和,flip后的和,就行了!
交换时,直接把两个值交换一下就行了。这真巧妙吧……
代码实现
出现一些问题:flip的lazy_tag究竟代表下面两个的哪一个?
- u的子树,含u,全都要flip
- u的子树,不含u,要flip
两者实现都简单。但是,pushup的时候,会出现问题:我们默认那些值都是真实值,所以调用到u时,u的值必须是真实的。所以实现要采用2.
之所以文艺平衡树没有出这问题,是因为和u无关……对吧。线段树也是这么实现的。
Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=998244353;
const int N=400005;
/*
a b
c d
*/
struct mat{
ll a,b,c,d;
mat(int _a=1,int _b=0,int _c=0,int _d=1){a=_a; b=_b; c=_c; d=_d;}
void print(){
printf("%lld %lld\n%lld %lld\n",a,b,c,d);
}
};
mat operator *(mat x,mat y)
{
return mat(((x.a*y.a%mod+x.b*y.c%mod)%mod+mod)%mod,\
((x.a*y.b%mod+x.b*y.d%mod)%mod+mod)%mod,\
((x.c*y.a%mod+x.d*y.c%mod)%mod+mod)%mod,\
((x.c*y.b%mod+x.d*y.d%mod)%mod+mod)%mod);
}
mat W=mat(1,0,1,1),E=mat(2,1,mod-1,0);
mat A0=mat(0,1,1,0),A1=mat(1,1,1,0),END=mat(1,0,0,0);
mat I=mat(1,0,0,1);
//flip: W <-> E
//Reverse: [abc] <-> [cba]
mat v[N],v_fp[N];
int w[N];
int sz[N],lc[N],rc[N];
mat sum[N],sum_rev[N],sum_fp[N],sum_rev_fp[N];
bool rev[N],fp[N];
int rt=0,num=0;
void up(int u)
{
sz[u]=sz[lc[u]]+1+sz[rc[u]];
sum[u] = sum[lc[u]] * v[u] * sum[rc[u]];
sum_fp[u] = sum_fp[lc[u]] * v_fp[u] * sum_fp[rc[u]];
sum_rev[u] = sum_rev[rc[u]] * v[u] * sum_rev[lc[u]];
sum_rev_fp[u] = sum_rev_fp[rc[u]] * v_fp[u] * sum_rev_fp[lc[u]];
}
void apply_fp(int u)
{
fp[u]^=1;
swap(v[u],v_fp[u]);
swap(sum[u],sum_fp[u]);
swap(sum_rev[u],sum_rev_fp[u]);
}
void apply_rev(int u)
{
rev[u]^=1;
swap(lc[u],rc[u]);
swap(sum[u],sum_rev[u]);
swap(sum_fp[u],sum_rev_fp[u]);
}
void down(int u)
{
if(!u) return;
if(fp[u])
{
if(lc[u]) apply_fp(lc[u]);
if(rc[u]) apply_fp(rc[u]);
fp[u]=0;
}
if(rev[u])
{
if(lc[u]) apply_rev(lc[u]);
if(rc[u]) apply_rev(rc[u]);
rev[u]=0;
}
}
int new_node(bool is_W)
{
++num;
w[num]=rand(); sz[num]=1;
if(is_W) {v[num]=W; v_fp[num]=E;}
else {v[num]=E; v_fp[num]=W;}
sum[num]=sum_rev[num]=v[num];
sum_fp[num]=sum_rev_fp[num]=v_fp[num];
return num;
}
void split(int u,int k,int& x,int& y)
{
if(!u){x=y=0; return;}
down(u);
if(sz[lc[u]]<k){
x=u;
split(rc[u],k-sz[lc[u]]-1,rc[u],y);
}
else{
y=u;
split(lc[u],k,x,lc[u]);
}
up(u);
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
down(x); down(y);
if(w[x]<w[y]){
rc[x]=merge(rc[x],y);
up(x);
return x;
}
else
{
lc[y]=merge(x,lc[y]);
up(y);
return y;
}
}
void insert(int k,bool is_W)
{
int x,y;
split(rt,k-1,x,y);
rt=merge(merge(x,new_node(is_W)),y);
}
void flip(int l,int r)
{
int a,b,c;
split(rt,r,b,c);
split(b,l-1,a,b);
apply_fp(b);
rt=merge(merge(a,b),c);
}
void reverse(int l,int r)
{
int a,b,c;
split(rt,r,b,c);
split(b,l-1,a,b);
apply_rev(b);
rt=merge(merge(a,b),c);
}
void append(bool is_W)
{
rt=merge(rt,new_node(is_W));
}
void get_ans()
{
down(rt);
mat ret=A0*A1*sum[rt]*END;
printf("%lld %lld\n",ret.a,ret.c);
}
void init()
{
sum[0]=sum_rev[0]=sum_fp[0]=sum_rev_fp[0]=v[0]=v_fp[0]=I;
lc[0]=rc[0]=0;
}
int n,m;
char str[25],ss[N];//WHAT THE F**K?!?!?!?!?!
int main()
{
//freopen("P7739_1.in","r",stdin);
//freopen("out.txt","w",stdout);
srand(time(0));
scanf("%d%d",&n,&m);
scanf("%s",ss);//ARE YOU SURE?!?!?!
init();
int l,r;
for(int i=0;i<n;i++) append(ss[i]=='W');
get_ans();
while(m--)
{
scanf("%s",str);
if(str[0]=='A'){
scanf("%s",str);
append(str[0]=='W');
}
if(str[0]=='F'){
scanf("%d%d",&l,&r);
flip(l,r);
}
if(str[0]=='R'){
scanf("%d%d",&l,&r);
reverse(l,r);
}
get_ans();
}
return 0;
}
本文来自博客园,作者:B_F_S,转载请注明原文链接:https://www.cnblogs.com/Na2SO4/p/23083299

浙公网安备 33010602011771号