题解 P7739 [NOI2021D2T2] 密码箱

最近正在学 FHQ-Treap 和高等代数,隐约记得ix35这题用平衡树做的,遂来挑战。

显然凭一己之力是做不出来的,于是瞄了一眼题解,让我十分震惊。但说真的,用矩阵来理解连分数算是Adhoc吧。

Problem

  1. 初始时数列长度为 \(2\) 且有 \(a_0 = 0, a_1 = 1\);
  2. 对数列依次进行若干次操作,其中每次操作是以下两种类型之一:
  • W 类型:给数列的最后一项加 \(1\)。
  • E 类型:若数列的最后一项为 \(1\),则给倒数第二项加 \(1\);否则先给数列的最后一项减 \(1\),接着在数列尾再加两项,两项的值都是 \(1\)。

\[f(a_0, \ldots , a_{k - 1}, a_k) = \begin{cases} a_0, & k = 0 \\ f \! \left( a_0, a_1, \ldots , a_{k - 2}, a_{k - 1} + \frac{1}{a_k} \right) \! , & k \ge 1 \end{cases} \]

  • 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\) 后的余数。

转化成矩阵问题

\[\frac{p_0}{q_0}=a_0+\frac{1}{\frac{p_1}{q_1}}=\frac{a_0p_1+q_1}{p_1} \]

因为两者可以线性表示,我们可以得到

\[\begin{bmatrix}p_0\\q_0\end{bmatrix}=\begin{bmatrix}a_0&1\\1&0\end{bmatrix}\times\begin{bmatrix}p_1\\q_1\end{bmatrix} \]

所以我们要求的东西就是

\[\begin{bmatrix}a_0&1\\1&0\end{bmatrix}\begin{bmatrix}a_1&1\\1&0\end{bmatrix}\cdots\begin{bmatrix}a_0k&1\\1&0\end{bmatrix}\begin{bmatrix}1\\0\end{bmatrix} \]

注意到 W 和 E 都是对末尾几位操作,我们可以考虑把两者都变成在最后右乘一个矩阵。

对于 W,我们要找

\[\begin{bmatrix}{a+1}&1\\ 1&0\end{bmatrix}=\begin{bmatrix}a&1\\1&0\end{bmatrix}\begin{bmatrix}?&?\\?&?\end{bmatrix} \]

为此,我们要求一个\(A^{-1}B\)型的矩阵,可以写成增广矩阵 \((A|B)\) 并初等行变换得到\((I| A^{-1}B)\)

\[\left(\begin{array}{cc|cc} a&1&a+1&1\\ 1&0&1&0\end{array} \right) \xrightarrow{R1\leftrightarrow R2} \left(\begin{array}{cc|cc}1&0&1&0 \\ a&1&a+1&1\end{array} \right) \xrightarrow{R2-a\times R1} \left(\begin{array}{cc|cc}1&0&1&0 \\ 0&1&1&1\end{array} \right) \]

解得 \(W=\begin{bmatrix}1&0\\1&1\end{bmatrix}\)

对于 E,先看最后一项不是1的情况。仿照W的解法,我们得到

\[\begin{bmatrix}1&0\\-1&1\end{bmatrix}\begin{bmatrix}1&1\\1&0\end{bmatrix}\begin{bmatrix}1&1\\1&0\end{bmatrix}=\begin{bmatrix}2&1\\-1&0\end{bmatrix} \]

我们猜想另外一种情况也是如此,也就是我们希望

\[\begin{bmatrix}a&1\\1&0\end{bmatrix}\begin{bmatrix}1&1\\1&0\end{bmatrix}\begin{bmatrix}2&1\\-1&0\end{bmatrix}=\begin{bmatrix}a+1&1\\1&0\end{bmatrix}\begin{bmatrix}1&1\\1&0\end{bmatrix} \]

这是正确的。

因此,我们做到了,\(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究竟代表下面两个的哪一个?

  1. u的子树,含u,全都要flip
  2. 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;
}
posted @ 2026-09-22 18:41  B_F_S  阅读(3)  评论(0)    收藏  举报