[NOI2021] 密码箱——连分数、动态DP

[NOI2021] 密码箱

前言

我大概是全场(同步赛)唯一一个靠找规律做出这题的吧。
挺刺激的,最后三分钟肉眼找到Treap的最后一个错误。
我虽然考前从这篇博客了解过连分数,不过当时压根没想过NOI会考这个,连递推式都搞忘了。

思路

观察函数 f f f 的求法,其实就是在算连分数。设第 i i i 渐进分数为 p i q i \frac{p_i}{q_i} qipi,数列第 i i i 项为 a i a_i ai,那么有如下递推关系:
p i = a i p i − 1 + p i − 2 q i = a i q i − 1 + q i − 2 p_i=a_ip_{i-1}+p_{i-2}\\q_i=a_iq_{i-1}+q_{i-2} pi=aipi1+pi2qi=aiqi1+qi2
我们要求的就是 p n − 1 q n − 1 \frac{p_{n-1}}{q_{n-1}} qn1pn1 。由于连分数保证了是最简分数,所以
由于分子分母的的递推式完全一样,且互不相干,所以我们只需用不同的初值、同样的方法把分子分母各求一遍。

(下面只讨论分子)容易发现下一个分子只与前两个分子有关,于是设当前最后两个分子为 { a , b } \{a,b\} {a,b},我们分WE讨论它的变化情况:

  • 一次W操作,序列末尾从 { a a , a b } \{a_a,a_b\} {aa,ab} 变为 { a a , a b + 1 } \{a_a,a_b+1\} {aa,ab+1},根据递推式,分子变为 { a , a + b } \{a,a+b\} {a,a+b}
  • 一次E操作,又要分两种情况:
    • 最后一项不为1,序列尾变为 { a a , a b − 1 , 1 , 1 } \{a_a,a_b-1,1,1\} {aa,ab1,1,1} ,所以最后两个分子发生如下变化: { a , b } → { a , b − a } → { b − a , b } → { b , 2 b − a } \{a,b\}\rightarrow \{a,b-a\}\rightarrow \{b-a,b\}\rightarrow \{b,2b-a\} {a,b}{a,ba}{ba,b}{b,2ba}
    • 最后一项为1,那么假设倒数第3个分子为 c c c,那么序列倒数第二项加一会导致最后两个分子变为 { a + c , b + c } \{a+c,b+c\} {a+c,b+c}。由于最后一项为1,所以保证 b = a + c b=a+c b=a+c,于是 a + c = b , b + c = 2 b − a a+c=b,b+c=2b-a a+c=b,b+c=2ba,最后两个分子还是变为了 { b , 2 b − a } \{b,2b-a\} {b,2ba}

于是我们发现,操作为WE时分子或分母的变化方式是固定且一次的,所以可以用矩阵乘法代替普通的DP,这题的转移就有了可动态维护的性质。

我们可以用平衡树维护操作序列每个位置和一段区间的转移矩阵,并同时维护它的相反矩阵和翻转矩阵(共4个矩阵),然后就可以实现在操作序列末尾加入新操作、反转序列区间、翻转序列区间等。这和普通动态DP用线段树维护不一样,带区间翻转操作的动态DP只能用平衡树。


关于我在考试时发现的规律,其实不是我上面写的这个转移式。

我把操作后 f f f 函数的计算结果的变化全部打下表来神奇
这让我想起了在做这道题时看到的某个卷毛香猪的博客里介绍的 S t e r n − B r o c o t \rm Stern-Brocot SternBrocot 树,同样是分数,同样是二叉树,同样以 1 1 \frac{1}{1} 11 为根。两者长得竟然一模一样
Stern-Brocot

S t e r n − B r o c o t \rm Stern-Brocot SternBrocot 树上的分数都有一个非常优美且简单的规律:分子或分母等于它两侧虚线上的分子或分母相加。

于是转移式子直接出来了!而且最简分数无需约分!直接套路动态DP啊!

于是我激动不已,以至于我打了一半线段树才突然想到用平衡树,代码成形后一堆错误,然后删删改改…


代码

连分数递推版

#include<cstdio>//JZM yyds!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<ctime>
#include<map>
#define ll long long
#define MAXN 200005
#define uns unsigned
#define INF 1e17
#define MOD 998244353ll
#define lowbit(x) ((x)&(-(x)))
#define ull uns ll
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
inline ll RAND(){return 1ll*rand()*rand();}
int n,q,m;
char s[MAXN];
struct matrix{
	int n,m;
	ll c[2][2];
	matrix(){memset(c,0,sizeof(c)),n=m=0;}
	matrix operator*(const matrix&b){
		matrix r;r.n=n,r.m=b.m;
		for(int i=0;i<n;i++)
			for(int k=0;k<m;k++)
				for(int j=0;j<r.m;j++)
					r.c[i][j]=(r.c[i][j]+c[i][k]*b.c[k][j])%MOD;
		return r;
	}
}E,A,B,aa,bb,f,g;
struct node{
	int x,y;node(){}
	node(int X,int Y){x=X,y=Y;}
};
struct itn{
	int ls,rs,siz,val;
	matrix a,c,sa,sb,sc,sd;
	bool p,q;
	itn(){a=c=sa=sb=sc=sd=E,p=q=0,ls=rs=0,siz=0;}
}t[MAXN];
int root,IN;
inline void PD(int x){
	if(t[x].p){
		if(t[x].ls)
		swap(t[t[x].ls].a,t[t[x].ls].c),
		swap(t[t[x].ls].sa,t[t[x].ls].sc),swap(t[t[x].ls].sb,t[t[x].ls].sd),
		t[t[x].ls].p^=1;
		if(t[x].rs)
		swap(t[t[x].rs].a,t[t[x].rs].c),
		swap(t[t[x].rs].sa,t[t[x].rs].sc),swap(t[t[x].rs].sb,t[t[x].rs].sd),
		t[t[x].rs].p^=1;
		t[x].p=0;
	}
	if(t[x].q){
		if(t[x].ls)swap(t[t[x].ls].sa,t[t[x].ls].sb),swap(t[t[x].ls].sc,t[t[x].ls].sd),
		t[t[x].ls].q^=1,swap(t[t[x].ls].ls,t[t[x].ls].rs);
		if(t[x].rs)swap(t[t[x].rs].sa,t[t[x].rs].sb),swap(t[t[x].rs].sc,t[t[x].rs].sd),
		t[t[x].rs].q^=1,swap(t[t[x].rs].ls,t[t[x].rs].rs);
		t[x].q=0;
	}
}
inline void update(int x){
	t[x].sa=t[t[x].ls].sa*t[x].a*t[t[x].rs].sa;
	t[x].sb=t[t[x].rs].sb*t[x].a*t[t[x].ls].sb;
	t[x].sc=t[t[x].ls].sc*t[x].c*t[t[x].rs].sc;
	t[x].sd=t[t[x].rs].sd*t[x].c*t[t[x].ls].sd;
	t[x].siz=t[t[x].ls].siz+t[t[x].rs].siz+1;
}
inline node split(int x,int k){
	if(!x||!k)return node(0,x);
	PD(x);node res;int si=t[t[x].ls].siz;
	if(si>=k)
		res=split(t[x].ls,k),t[x].ls=res.y,update(x),res.y=x;
	else
		res=split(t[x].rs,k-si-1),t[x].rs=res.x,update(x),res.x=x;
	return res;
}
inline int mergg(int x,int y){
	if(!x||!y)return x|y;
	PD(x),PD(y);int res;
	if(t[x].val<=t[y].val)
		t[x].rs=mergg(t[x].rs,y),update(x),res=x;
	else t[y].ls=mergg(x,t[y].ls),update(y),res=y;
	return res;
}
inline void CP(int x){
	t[x].sa=t[x].a,t[x].sb=t[x].a,t[x].sc=t[x].c,t[x].sd=t[x].c;
}
char in[15],cc;
ll ansa,ansb;
signed main()
{
// 	freopen("code.in","r",stdin);
// 	freopen("code.out","w",stdout);
	srand(time(0));
	A.n=A.m=B.n=B.m=2,E.n=E.m=2;
	E.c[0][0]=E.c[1][1]=1;
	A.c[0][0]=A.c[0][1]=A.c[1][1]=1;
	B.c[0][1]=MOD-1,B.c[1][0]=1,B.c[1][1]=2;
	aa.n=bb.n=1,aa.m=bb.m=2;
	aa.c[0][1]=bb.c[0][0]=bb.c[0][1]=1;
	t[0]=itn();
	n=read(),q=read(),m=n+q;
	scanf("%s",s+1);
	for(int i=n+1;i<=m;i++)s[i]='W';
	for(int i=1;i<=n;i++){
		IN++,t[IN]=itn(),t[IN].siz=1;
		if(s[i]=='W')t[IN].a=A,t[IN].c=B;
		else t[IN].a=B,t[IN].c=A;
		CP(IN),t[IN].val=RAND()%MAXN,root=mergg(root,IN);
	}
	f=t[root].sa;
	g=aa*f,ansa=g.c[0][1];
	g=bb*f,ansb=g.c[0][1];
	printf("%lld %lld\n",ansa,ansb);
	while(q--){
		scanf("%s",in);
		if(in[0]=='A'){
			cc=getchar();
			while(cc!='W'&&cc!='E')cc=getchar();
			n++,s[n]=cc,IN++,t[IN]=itn(),t[IN].siz=1;
			if(s[n]=='W')t[IN].a=A,t[IN].c=B;
			else t[IN].a=B,t[IN].c=A;
			CP(IN),t[IN].val=RAND()%MAXN,root=mergg(root,IN);
		}
		else if(in[0]=='F'){
			int l=read(),r=read();
			node x=split(root,l-1),y=split(x.y,r-l+1);
			t[y.x].p^=1;
			swap(t[y.x].a,t[y.x].c),
			swap(t[y.x].sa,t[y.x].sc),swap(t[y.x].sb,t[y.x].sd);
			root=mergg(x.x,mergg(y.x,y.y));
		}
		else{
			int l=read(),r=read();
			node x=split(root,l-1),y=split(x.y,r-l+1);
			t[y.x].q^=1;
			swap(t[y.x].sa,t[y.x].sb),swap(t[y.x].sc,t[y.x].sd);
			swap(t[y.x].ls,t[y.x].rs);
			root=mergg(x.x,mergg(y.x,y.y));
		}
		f=t[root].sa;
		g=aa*f,ansa=g.c[0][1];
		g=bb*f,ansb=g.c[0][1];
		printf("%lld %lld\n",ansa,ansb);
	}
	return 0;
}

S t e r n − B r o c o t \rm Stern-Brocot SternBrocot 树版(两者只有初始和转移矩阵及提取答案的区别)

#include<cstdio>//JZM yyds!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<ctime>
#include<map>
#define ll long long
#define MAXN 200005
#define uns unsigned
#define INF 1e17
#define MOD 998244353ll
#define lowbit(x) ((x)&(-(x)))
#define ull uns ll
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
inline ll RAND(){return 1ll*rand()*rand();}
int n,q,m;
char s[MAXN];
struct matrix{
	int n,m;
	ll c[2][2];
	matrix(){memset(c,0,sizeof(c)),n=m=0;}
	matrix operator*(const matrix&b){
		matrix r;r.n=n,r.m=b.m;
		for(int i=0;i<n;i++)
			for(int k=0;k<m;k++)
				for(int j=0;j<r.m;j++)
					r.c[i][j]=(r.c[i][j]+c[i][k]*b.c[k][j])%MOD;
		return r;
	}
}E,A,B,aa,bb,f,g;
struct node{
	int x,y;node(){}
	node(int X,int Y){x=X,y=Y;}
};
struct itn{
	int ls,rs,siz,val;
	matrix a,c,sa,sb,sc,sd;
	bool p,q;
	itn(){a=c=sa=sb=sc=sd=E,p=q=0,ls=rs=0,siz=0;}
}t[MAXN];
int root,IN;
inline void PD(int x){
	if(t[x].p){
		if(t[x].ls)
		swap(t[t[x].ls].a,t[t[x].ls].c),
		swap(t[t[x].ls].sa,t[t[x].ls].sc),swap(t[t[x].ls].sb,t[t[x].ls].sd),
		t[t[x].ls].p^=1;
		if(t[x].rs)
		swap(t[t[x].rs].a,t[t[x].rs].c),
		swap(t[t[x].rs].sa,t[t[x].rs].sc),swap(t[t[x].rs].sb,t[t[x].rs].sd),
		t[t[x].rs].p^=1;
		t[x].p=0;
	}
	if(t[x].q){
		if(t[x].ls)swap(t[t[x].ls].sa,t[t[x].ls].sb),swap(t[t[x].ls].sc,t[t[x].ls].sd),
		t[t[x].ls].q^=1,swap(t[t[x].ls].ls,t[t[x].ls].rs);
		if(t[x].rs)swap(t[t[x].rs].sa,t[t[x].rs].sb),swap(t[t[x].rs].sc,t[t[x].rs].sd),
		t[t[x].rs].q^=1,swap(t[t[x].rs].ls,t[t[x].rs].rs);
		t[x].q=0;
	}
}
inline void update(int x){
	t[x].sa=t[t[x].ls].sa*t[x].a*t[t[x].rs].sa;
	t[x].sb=t[t[x].rs].sb*t[x].a*t[t[x].ls].sb;
	t[x].sc=t[t[x].ls].sc*t[x].c*t[t[x].rs].sc;
	t[x].sd=t[t[x].rs].sd*t[x].c*t[t[x].ls].sd;
	t[x].siz=t[t[x].ls].siz+t[t[x].rs].siz+1;
}
inline node split(int x,int k){
	if(!x||!k)return node(0,x);
	PD(x);node res;int si=t[t[x].ls].siz;
	if(si>=k)
		res=split(t[x].ls,k),t[x].ls=res.y,update(x),res.y=x;
	else
		res=split(t[x].rs,k-si-1),t[x].rs=res.x,update(x),res.x=x;
	return res;
}
inline int mergg(int x,int y){
	if(!x||!y)return x|y;
	PD(x),PD(y);int res;
	if(t[x].val<=t[y].val)
		t[x].rs=mergg(t[x].rs,y),update(x),res=x;
	else t[y].ls=mergg(x,t[y].ls),update(y),res=y;
	return res;
}
inline void CP(int x){
	t[x].sa=t[x].a,t[x].sb=t[x].a,t[x].sc=t[x].c,t[x].sd=t[x].c;
}
char in[15],cc;
ll ansa,ansb;
signed main()
{
// 	freopen("code.in","r",stdin);
// 	freopen("code.out","w",stdout);
	srand(time(0));
	A.n=A.m=B.n=B.m=2,E.n=E.m=2;
	E.c[0][0]=E.c[1][1]=1;
	A.c[0][0]=A.c[0][1]=A.c[1][1]=1;
	B.c[0][0]=B.c[1][0]=B.c[1][1]=1;
	aa.n=bb.n=1,aa.m=bb.m=2;
	aa.c[0][1]=bb.c[0][0]=1;
	t[0]=itn();
	n=read(),q=read(),m=n+q;
	scanf("%s",s+1);
	for(int i=n+1;i<=m;i++)s[i]='W';
	for(int i=1;i<=n;i++){
		IN++,t[IN]=itn(),t[IN].siz=1;
		if(s[i]=='W')t[IN].a=A,t[IN].c=B;
		else t[IN].a=B,t[IN].c=A;
		CP(IN),t[IN].val=RAND()%MAXN,root=mergg(root,IN);
	}
	f=t[root].sa;
	g=aa*f,ansa=(g.c[0][1]+g.c[0][0])%MOD;
	g=bb*f,ansb=(g.c[0][1]+g.c[0][0])%MOD;
	printf("%lld %lld\n",ansa,ansb);
	while(q--){
		scanf("%s",in);
		if(in[0]=='A'){
			cc=getchar();
			while(cc!='W'&&cc!='E')cc=getchar();
			n++,s[n]=cc,IN++,t[IN]=itn(),t[IN].siz=1;
			if(s[n]=='W')t[IN].a=A,t[IN].c=B;
			else t[IN].a=B,t[IN].c=A;
			CP(IN),t[IN].val=RAND()%MAXN,root=mergg(root,IN);
		}
		else if(in[0]=='F'){
			int l=read(),r=read();
			node x=split(root,l-1),y=split(x.y,r-l+1);
			t[y.x].p^=1;
			swap(t[y.x].a,t[y.x].c),
			swap(t[y.x].sa,t[y.x].sc),swap(t[y.x].sb,t[y.x].sd);
			root=mergg(x.x,mergg(y.x,y.y));
		}
		else{
			int l=read(),r=read();
			node x=split(root,l-1),y=split(x.y,r-l+1);
			t[y.x].q^=1;
			swap(t[y.x].sa,t[y.x].sb),swap(t[y.x].sc,t[y.x].sd);
			swap(t[y.x].ls,t[y.x].rs);
			root=mergg(x.x,mergg(y.x,y.y));
		}
		f=t[root].sa;
		g=aa*f,ansa=(g.c[0][1]+g.c[0][0])%MOD;
		g=bb*f,ansb=(g.c[0][1]+g.c[0][0])%MOD;
		printf("%lld %lld\n",ansa,ansb);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值