连分数,Stern-Brocot 树和 Farey 序列 学习笔记
xujindong_ · · 个人记录
连分数,Stern-Brocot 树和 Farey 序列是三种维护有理分数的结构,三者有很强的联系。
连分数
对于数列
此处我们考虑系数全为整数,且
不难发现,因为
考虑一个连分数的渐进分数怎么求。注意到当
则
P10788
只考虑
发现这个过程就是连分数
#include<bits/stdc++.h>
using namespace std;
int n,m;
long long dfs(int a,int b){
long long ans=(a<=m&&b<=n)+(a<=n&&b<=m);
for(a+=2*b;a<=max(n,m);a+=2*b)ans+=dfs(b,a);
return ans;
}
int main(){
return cin>>n>>m,cout<<dfs(0,1)-2<<'\n',0;
}
考虑优化成一次计算多个连分数。我们给连分数的某个位置留空,钦定最大值(多个最大取最前/后)不填。此时我们可以把搜出来的连分数表示为
#include<bits/stdc++.h>
using namespace std;
int n,m;
long long dfs(int a,int b,int c,int d,int lim,bool type){
if(1ll*a*lim+b>n)return 0;
long long ans=type?min(a?(n-b)/a:m,(m-d)/c)-lim+1+max((n-d)/c-lim+1,0):dfs(0,d,2*d,b,lim,1);
for(int i=1;(type?1ll*(a+2*i*c)*max(lim,i+1)+b+2*i*d:2ll*(b+2*i*d)*max(lim,i)+d)<=m;i++)ans+=dfs(c,d,a+2*i*c,b+2*i*d,max(lim,i+type),type);
return ans;
}
int main(){
cin>>n>>m;
if(n>m)swap(n,m);
return cout<<dfs(0,0,0,1,1,0)<<'\n',0;
}
P7739
显然
但是这题维护的是操作序列而不是连分数。考虑将操作序列也看作线性变换。对操作一,从
至此问题变为维护矩阵的乘积。平衡树维护,有反转和翻转的标记,对应的要维护四个信息对应标记的四种状态,下传时交换这些矩阵。
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int n,m,rt;
string opt,s;
struct matrix{
int w,x,y,z;
matrix(){
w=z=1,x=y=0;
}
matrix(int a,int b,int c,int d){
w=a,x=b,y=c,z=d;
}
matrix operator*(matrix a){
return matrix((1ll*w*a.w+1ll*x*a.y)%mod,(1ll*w*a.x+1ll*x*a.z)%mod,(1ll*y*a.w+1ll*z*a.y)%mod,(1ll*y*a.x+1ll*z*a.z)%mod);
}
};
const matrix W(1,1,0,1),E(0,mod-1,1,2);
struct node{
int tr[2],sz,r;
bool tag1,tag2;
matrix v0,v1,p00,p01,p10,p11;
};
template<int maxn>struct FHQtreap{
node tr[maxn];
int cnt;
void pushdown1(int pos){
if(pos)tr[pos].tag1^=1,swap(tr[pos].tr[0],tr[pos].tr[1]),swap(tr[pos].p00,tr[pos].p10),swap(tr[pos].p01,tr[pos].p11);
}
void pushdown2(int pos){
if(pos)tr[pos].tag2^=1,swap(tr[pos].v0,tr[pos].v1),swap(tr[pos].p00,tr[pos].p01),swap(tr[pos].p10,tr[pos].p11);
}
void pushdown(int pos){
if(tr[pos].tag1)pushdown1(tr[pos].tr[0]),pushdown1(tr[pos].tr[1]),tr[pos].tag1=0;
if(tr[pos].tag2)pushdown2(tr[pos].tr[0]),pushdown2(tr[pos].tr[1]),tr[pos].tag2=0;
}
void pushup(int pos){
tr[pos].sz=tr[tr[pos].tr[0]].sz+tr[tr[pos].tr[1]].sz+1;
tr[pos].p00=tr[tr[pos].tr[0]].p00*tr[pos].v0*tr[tr[pos].tr[1]].p00;
tr[pos].p01=tr[tr[pos].tr[0]].p01*tr[pos].v1*tr[tr[pos].tr[1]].p01;
tr[pos].p10=tr[tr[pos].tr[1]].p10*tr[pos].v0*tr[tr[pos].tr[0]].p10;
tr[pos].p11=tr[tr[pos].tr[1]].p11*tr[pos].v1*tr[tr[pos].tr[0]].p11;
}
void split(int pos,int k,int&a,int&b){
if(!pos){
a=b=0;
return;
}
pushdown(pos);
if(tr[tr[pos].tr[0]].sz<k)a=pos,split(tr[pos].tr[1],k-tr[tr[pos].tr[0]].sz-1,tr[a].tr[1],b);
else b=pos,split(tr[pos].tr[0],k,a,tr[b].tr[0]);
pushup(pos);
}
int merge(int a,int b){
if(!a||!b)return a^b;
if(tr[a].r<tr[b].r)return pushdown(a),tr[a].tr[1]=merge(tr[a].tr[1],b),pushup(a),a;
else return pushdown(b),tr[b].tr[0]=merge(a,tr[b].tr[0]),pushup(b),b;
}
void append(int&rt,char opt){
tr[++cnt].sz=1,tr[cnt].r=rand(),tr[cnt].v0=tr[cnt].p00=tr[cnt].p10=opt=='W'?W:E,tr[cnt].v1=tr[cnt].p01=tr[cnt].p11=opt=='W'?E:W,rt=merge(rt,cnt);
}
void reverse(int&rt,int l,int r,int a=0,int b=0,int c=0){
split(rt,l-1,a,b),split(b,r-l+1,b,c),pushdown1(b),rt=merge(merge(a,b),c);
}
void flip(int&rt,int l,int r,int a=0,int b=0,int c=0){
split(rt,l-1,a,b),split(b,r-l+1,b,c),pushdown2(b),rt=merge(merge(a,b),c);
}
};
FHQtreap<200005>t;
int main(){
ios::sync_with_stdio(0),cin.tie(0),srand(time(0)),cin>>n>>m>>s;
for(int i=0;i<n;i++)t.append(rt,s[i]);
cout<<t.tr[rt].p00.z<<' '<<(t.tr[rt].p00.x+t.tr[rt].p00.z)%mod<<'\n';
for(int i=1,l,r;i<=m;i++){
cin>>opt;
if(opt[0]=='A')cin>>s,t.append(rt,s[0]);
else if(opt[0]=='F')cin>>l>>r,t.flip(rt,l,r);
else cin>>l>>r,t.reverse(rt,l,r);
cout<<t.tr[rt].p00.z<<' '<<(t.tr[rt].p00.x+t.tr[rt].p00.z)%mod<<'\n';
}
return 0;
}
Stern-Brocot 树
定义 Stern-Brocot 树的开始是两个分数
假如取出每一层新增的数,会形成一个完全二叉树结构。
Stern-Brocot 树的每一行都是单调递增的,且每个分数都是最简分数(
因此,Stern-Brocot 树可以看成二叉查找树,可以在上面二分。在树上走的过程可以看作有一个三元组
值得注意的是,查找一个数,拐弯是次数是
假如查找一个分数,有更简单的办法。不妨强制
若我们要求的数是未知的满足某个限制的数,直接二分步长,是
vector<pair<long long,char> >encode_path(long long p,long long q){
bool f=1;
vector<pair<long long,char> >ans;
while(q){
if(p>=q)ans.push_back(make_pair(p/q,f?'R':'L'));
p%=q,swap(p,q),f^=1;
}
if(!--ans.back().first)ans.pop_back();
return ans;
}
Stern-Brocot 树的另一个应用是双曲线下数点。一个经典问题是求
首先可以用狄利克雷双曲线法转化为
考虑用一个凸包拟合这个曲线,令
如图,向量
维护一个斜率单调递减的栈,初始为
设
当
推广到
用类欧或万欧是可行的。更好的方式是,整个过程中我们走的向量都由先前的两个向量
我们也可以拟合其他的曲线,上述算法的条件是曲线下凸且斜率总不小于
P5179
考虑在 Stern-Brocot 树上二分,小于下界就向右,大于上界就向左。这个最坏是
计算一段连续向右的次数,解不等式
#include<bits/stdc++.h>
using namespace std;
int t,A,B,C,D;
void solve(int a,int b,int c,int d){
int x=a+c,y=b+d,temp;
if(1ll*A*y<1ll*B*x&&1ll*x*D<1ll*y*C){
cout<<x<<'/'<<y<<'\n';
return;
}
if(1ll*A*y>=1ll*B*x)temp=(1ll*A*y-1ll*B*x)/(1ll*B*c-1ll*A*d),solve(x+temp*c,y+temp*d,c,d);
else temp=(1ll*C*y-1ll*D*x)/(1ll*D*a-1ll*C*b),solve(a,b,x+temp*a,y+temp*b);
}
int main(){
while(cin>>A>>B>>C>>D)solve(0,1,1,0);
return 0;
}
P1797
上文我们
#include<bits/stdc++.h>
using namespace std;
int t,p,q,k,u,v;
char c;
string opt;
vector<pair<int,char> >encode_path(int p,int q){
bool f=1;
vector<pair<int,char> >ans;
while(q){
if(p>=q)ans.push_back(make_pair(p/q,f?'R':'L'));
p%=q,swap(p,q),f^=1;
}
if(!--ans.back().first)ans.pop_back();
return ans;
}
pair<pair<int,int>,pair<int,int> >range(vector<pair<int,char> >e){
int a=0,b=1,c=1,d=0;
for(int i=0;i<e.size();i++){
if(e[i].second=='L')c+=a*e[i].first,d+=b*e[i].first;
else a+=c*e[i].first,b+=d*e[i].first;
}
return make_pair(make_pair(a,b),make_pair(c,d));
}
pair<int,int>decode_path(vector<pair<int,char> >e){
pair<pair<int,int>,pair<int,int> >temp=range(e);
return make_pair(temp.first.first+temp.second.first,temp.first.second+temp.second.second);
}
pair<int,int>lca(int a,int b,int c,int d){
vector<pair<int,char> >e,e1=encode_path(a,b),e2=encode_path(c,d);
for(int i=0;i<min(e1.size(),e2.size());i++){
if(e1[i]==e2[i])e.push_back(e1[i]);
else if(e1[i].second!=e2[i].second)break;
else{
e.push_back(make_pair(min(e1[i].first,e2[i].first),e1[i].second));
break;
}
}
return decode_path(e);
}
pair<int,int>ancestor(int p,int q,int k){
int dep=0;
vector<pair<int,char> >e=encode_path(p,q);
for(int i=0;i<e.size();i++)dep+=e[i].first;
if(k>dep)return make_pair(-1,-1);
k=dep-k;
while(k){
if(k>=e.back().first)k-=e.back().first,e.pop_back();
else e.back().first-=k,k=0;
}
return decode_path(e);
}
int main(){
cin>>t;
while(t--){
cin>>opt;
if(opt=="ENCODE_PATH"){
cin>>p>>q;
vector<pair<int,char> >e=encode_path(p,q);
cout<<e.size()<<' ';
for(int i=0;i<e.size();i++)cout<<e[i].second<<' '<<e[i].first<<' ';
putchar('\n');
}
else if(opt=="DECODE_PATH"){
vector<pair<int,char> >e;
cin>>k;
for(int i=0;i<k;i++)cin>>c>>p,e.push_back(make_pair(p,c));
pair<int,int>temp=decode_path(e);
cout<<temp.first<<' '<<temp.second<<'\n';
}
else if(opt=="LCA"){
cin>>p>>q>>u>>v;
pair<int,int>temp=lca(p,q,u,v);
cout<<temp.first<<' '<<temp.second<<'\n';
}
else if(opt=="ANCESTOR"){
cin>>k>>p>>q;
pair<int,int>temp=ancestor(p,q,k);
if(temp.first==-1)puts("-1");
else cout<<temp.first<<' '<<temp.second<<'\n';
}
else if(opt=="RANGE"){
cin>>p>>q;
vector<pair<int,char> >e=encode_path(p,q);
pair<pair<int,int>,pair<int,int> >temp=range(e);
cout<<temp.first.first<<' '<<temp.first.second<<' '<<temp.second.first<<' '<<temp.second.second<<'\n';
}
}
return 0;
}
SP26073
双曲线下数点模板题。
#include<bits/stdc++.h>
using namespace std;
template<typename T>void out(T x){
if(x>9)out(x/10);
putchar(x%10+'0');
}
int t,top;
long long n;
struct vec{
long long x,y;
}st[1000005];
__int128 solve(long long n,__int128 ans=0){
long long bn=sqrt(n),bn1=cbrt(n),x=n/bn+1,y=bn;
top=0,st[++top]=(vec){1,0},st[++top]=(vec){1,1};
while(1){
vec l=st[top],r=st[--top];
while((__int128)(x+l.x)*(y-l.y)>n)ans+=(__int128)x*l.y+(__int128)(l.x-1)*(l.y+1)/2-l.x,x+=l.x,y-=l.y;
if(y<=bn1)break;
while((__int128)(x+r.x)*(y-r.y)<=n)l=r,r=st[--top];
while(1){
vec mid=(vec){l.x+r.x,l.y+r.y};
if((__int128)(x+mid.x)*(y-mid.y)>n)st[++top]=mid,r=mid;
else{
if((__int128)n*r.x<=(__int128)(x+mid.x)*(x+mid.x)*r.y)break;
l=mid;
}
}
}
for(int i=1;i<=y;i++)ans+=n/i;
return 2*ans-bn*bn;
}
int main(){
cin>>t;
while(t--)cin>>n,out(solve(n)),putchar('\n');
return 0;
}
SP33039
双曲线法转化为
展开走一步的贡献:
展开
#include<bits/stdc++.h>
using namespace std;
template<typename T>void out(T x){
if(x>9)out(x/10);
putchar(x%10+'0');
}
int t,top;
long long n;
struct vec{
long long x,y;
__int128 f01,f11,f02;
}st[1000005];
__int128 solve(long long n){
long long bn=sqrt(n),bn1=cbrt(n),x=n/bn+1,y=bn;
__int128 ans=0;
top=0,st[++top]=(vec){1,0,0,0,0},st[++top]=(vec){1,1,0,0,0};
while(1){
vec l=st[top],r=st[--top];
while((__int128)(x+l.x)*(y-l.y)>n){
ans+=(__int128)l.y*x*y+y*l.f01-(__int128)l.y*(l.y-1)/2*x-l.f11;
ans+=((__int128)l.y*x*(x+1)+(2*x+1)*l.f01+l.f02)/2;
ans-=x+y;
x+=l.x,y-=l.y;
}
if(y<=bn1)break;
while((__int128)(x+r.x)*(y-r.y)<=n)l=r,r=st[--top];
while(1){
vec mid;
mid.x=l.x+r.x,mid.y=l.y+r.y;
mid.f01=l.f01+(__int128)l.x*r.y+r.f01;
mid.f11=l.f11+(__int128)l.x*r.y*(r.y-1)/2+r.f11+(__int128)r.y*l.y*l.x+l.y*r.f01;
mid.f02=l.f02+(__int128)r.y*l.x*l.x+(__int128)2*l.x*r.f01+r.f02;
if((__int128)(x+mid.x)*(y-mid.y)>n)r=mid,st[++top]=r;
else{
if((__int128)n*r.x<=(__int128)(x+mid.x)*(x+mid.x)*r.y)break;
l=mid;
}
}
}
for(int i=1;i<=y;i++)ans+=(__int128)i*(n/i)+(__int128)(n/i)*(n/i+1)/2;
return ans-(__int128)bn*bn*(bn+1)/2;
}
int main(){
cin>>t;
while(t--)cin>>n,out(solve(n)-(__int128)n*(n+1)/2),putchar('\n');
return 0;
}
P8058
先考虑怎么求一个分数的排名:
整除分块,
外层我们需要二分这个分数。考虑 Stern-Brocot 树上倍增,向左走时倍增第一个
#include<bits/stdc++.h>
using namespace std;
int bn,bn1,prime[1000005],mu[1000005];
long long n,k,pre[173210];
bool vis[1000005];
unordered_map<long long,int>sum;
int mu_init(int n,int prime[],int mu[],bool vis[],int cnt=0){
mu[1]=1;
for(int i=2;i<=n;i++)vis[i]=1;
for(int i=2;i<=n;i++){
if(vis[i])prime[++cnt]=i,mu[i]=-1;
for(int j=1,v,p;i*prime[j]<=n;j++){
p=prime[j],v=i*p,vis[v]=0;
if(i%p==0){
mu[v]=0;
break;
}
else mu[v]=-mu[i];
}
}
return cnt;
}
long long getsum(long long x){
if(x<=bn)return mu[x];
if(sum.count(x))return sum[x];
long long ans=1;
for(long long l=2,r;l<=x;l=r+1)r=x/(x/l),ans-=(r-l+1)*getsum(x/l);
return sum[x]=ans;
}
long long solve(long long n,long long a,long long b,long long c){
if(!a)return (n+1)*(b/c);
else if(a>=c||b>=c)return solve(n,a%c,b%c,c)+n*(n+1)/2*(a/c)+(n+1)*(b/c);
else return n*((a*n+b)/c)-solve((a*n+b)/c-1,c,c-b-1,a);
}
long long calc(long long p,long long q,long long ans=0){
for(int i=1;i<=bn1;i++)pre[i]=pre[i-1]+p*i/q;
for(long long l=1,r;l<=n;l=r+1)r=n/(n/l),ans+=(getsum(r)-getsum(l-1))*(n/l<=bn1?pre[n/l]:solve(n/l,p,0,q));
return ans;
}
int main(){
cin>>n>>k,bn=pow(n,2.0/3),bn1=sqrt(n*log2(n)),mu_init(bn,prime,mu,vis);
for(int i=1;i<=bn;i++)mu[i]+=mu[i-1];
bool f=1;
long long a=0,b=1,c=1,d=1;
while(1){
if(f){
int p=1;
while(calc(a+c*p+c,b+d*p+d)<k)p<<=1;
p>>=1,a+=c*p,b+=d*p;
for(p>>=1;p>=1;p>>=1)if(calc(a+c*p+c,b+d*p+d)<k)a+=c*p,b+=d*p;
if(calc(a+c,b+d)<k)a+=c,b+=d;
}
else{
int p=1;
while(calc(a+c+a*p,b+d+b*p)>k)p<<=1;
p>>=1,c+=a*p,d+=b*p;
for(p>>=1;p>=1;p>>=1)if(calc(a+c+a*p,b+d+b*p)>k)c+=a*p,d+=b*p;
if(calc(a+c,b+d)>k)c+=a,d+=b;
}
if(calc(a+c,b+d)==k)break;
f^=1;
}
return cout<<a+c<<' '<<b+d<<'\n',0;
}
P1676
我们用 Stern-Brocot 树去拟合正方形减掉圆形的一个角,即
我们需要先分成斜率
这个复杂度未知,但可以说明不超过
#include<bits/stdc++.h>
using namespace std;
template<typename T>void out(T x){
if(x>9)out(x/10);
putchar(x%10+'0');
}
int top;
long long n;
struct vec{
long long x,y;
}st[10000005];
bool checkin(long long x,long long y,long long n){
return y<0||(__int128)x*(2*n-x)<(__int128)(n-y)*(n-y);
}
long double der(long long x,long long n){
return (x-n)/sqrtl((__int128)x*(2*n-x));
}
__int128 solve(long long n,__int128 ans=0){
long long bn=(1-M_SQRT1_2)*n,bn1=sqrt(n),x=ceil(n-sqrtl((__int128)bn*(2*n-bn))),y=bn;
top=0,st[++top]=(vec){1,0},st[++top]=(vec){1,1};
while(y){
vec l=st[top],r=st[--top];
while(!checkin(x+l.x,y-l.y,n))ans+=(__int128)x*l.y+(__int128)(l.x-1)*(l.y+1)/2-l.x,x+=l.x,y-=l.y;
if(y<=bn1)break;
while(checkin(x+r.x,y-r.y,n))l=r,r=st[--top];
while(1){
vec mid=(vec){l.x+r.x,l.y+r.y};
if(!checkin(x+mid.x,y-mid.y,n))st[++top]=mid,r=mid;
else{
if(r.y>-r.x*der(x+mid.x,n))break;
l=mid;
}
}
}
for(int i=1;i<=y;i++)ans+=n-(__int128)sqrtl((__int128)i*(2*n-i))-1;
return 2*ans-(__int128)bn*bn;
}
int main(){
return cin>>n,out((__int128)4*n*n-4*solve(n)-4*n+5),0;
}
Farey 序列
定义 Farey 序列
考虑 Farey 序列和 Stern-Brocot 树的关系,在每次插入时,只需把产生的分母大于