Insert Addition
题解
前置芝士:Stern-Brocot 树与 Farey 序列
只是简要叙述,没啥用,建议跳过
Stern-Brocot树是一种维护分数的数据结构,树上的节点都是最简形式的分数,且像二叉查找树一样是有序的,在这棵树上我们可以
O
(
log
n
)
O\left(\log\,n\right)
O(logn)的查找到一个最简形式的分数。
与其说其是一颗树,它更像一个序列,最开始只有
{
0
1
,
1
0
}
\{\frac{0}{1},\frac{1}{0}\}
{10,01},再不断通过序列中相邻两项
a
/
b
a/b
a/b与
c
/
d
c/d
c/d像糖水混合一样得到新的点
a
+
b
c
+
d
\frac{a+b}{c+d}
c+da+b,通过糖水混合的原理,显然是有序的。
很明显,如果一直这样进行下去,这个序列将会变得无限大,而
F
a
r
e
y
Farey
Farey序列则是选取其中分母不超过某个值得点构成得序列。
很明显,这两者都是可以像线段树一样通过
O
(
n
log
n
)
O\left(n\log\,n\right)
O(nlogn)的时间复杂度生成,而对上面的节点,我们可以采用生成它的两个点
(
a
b
,
c
d
)
(\frac{a}{b},\frac{c}{d})
(ba,dc)的形式来表示,这种表示方法是唯一的,而且更方便它的遗传与变异。
如果你想了解更多与它相关的东西,可以参考上面的链接。
下面我们的做法讲涉及到它的几条性质。
进入正题
应该还不晚
由于本题要求的序列构建方式与Stern-Brocot树十分相似,我们很容易想到这上面去。
我们可以讲原题中的
a
a
a与
b
b
b转化成一个系数对
(
1
,
0
)
(1,0)
(1,0)与
(
0
,
1
)
(0,1)
(0,1),序列的生成也就像Stern-Brocot,变成了参数对的左右相加。
性质1
- 对于可以某种方式相邻的两点,它们的系数对
(
x
1
,
y
1
)
(x_1,y_1)
(x1,y1)与
(
x
2
,
y
2
)
(x_2,y_2)
(x2,y2)一定满足条件
x
1
y
2
−
x
2
y
1
=
1
x_1y_2-x_2y_1=1
x1y2−x2y1=1。
显然,这两者之间有一个是由另一个与其它的系数对一起构成的。
我们不妨设 ( x 2 , y 2 ) (x_2,y_2) (x2,y2)是由 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)与 ( x 3 , y 3 ) (x_3,y_3) (x3,y3)一起构成的,显然有
x 1 y 2 − x 2 y 1 = x 1 ( y 1 + y 3 ) − ( x 1 + x 3 ) y 1 = x 1 y 3 − x 3 y 1 x_1y_2-x_2y_1=x_1(y_1+y_3)-(x_1+x_3)y_1=x_1y_3-x_3y_1 x1y2−x2y1=x1(y1+y3)−(x1+x3)y1=x1y3−x3y1
显然,当系数对 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)与 ( x 3 , y 1 ) (x_3,y_1) (x3,y1)满足条件是,它满足条件。
一直这样追溯下去,最开始的系数对 ( 0 , 1 ) (0,1) (0,1)与 ( 1 , 0 ) (1,0) (1,0)显然是满足条件的,所以对于任意两个相邻的系数对,它们都满足这样的条件。
性质2
- 对于任意两个系数对
(
x
1
,
y
1
)
(x_1,y_1)
(x1,y1)与
(
x
2
,
y
2
)
(x_2,y_2)
(x2,y2),如果它们满足条件
x
1
y
2
−
x
2
y
1
=
1
x_1y_2-x_2y_1=1
x1y2−x2y1=1,它们在树上肯定可以以某种方式相邻。
这条性质的证明方法与上一条很像,可以请读者们自行推导。
推论
- 对于其中的任意一个节点的系数对
(
x
,
y
)
(x,y)
(x,y),一定满足
(
x
,
y
)
=
1
\left(x,y\right)=1
(x,y)=1(这里是最大公因数)。
这也就是我们之前说明Stern-Brocot树时的最简分数。
很明显,对于其中的任意一个系数对 ( x , y ) (x,y) (x,y),肯定与某一个系数对 ( z , w ) (z,w) (z,w)一起满足 x w − y z = 1 xw-yz=1 xw−yz=1。
如果 ( x , y ) ≠ 1 (x,y)\not = 1 (x,y)=1,显然有 ( x , y ) ( x w ( x , y ) − y z ( x , y ) ) = 1 (x,y)(\frac{xw}{(x,y)}-\frac{yz}{(x,y)})= 1 (x,y)((x,y)xw−(x,y)yz)=1, ( x w ( x , y ) − y z ( x , y ) ) (\frac{xw}{(x,y)}-\frac{yz}{(x,y)}) ((x,y)xw−(x,y)yz)明显是整数,而 ( x , y ) ≠ 1 (x,y)\not = 1 (x,y)=1,明显就矛盾了,所以 ( x , y ) (x,y) (x,y)一定是等于 1 1 1的。
由于生成
n
n
n次生成出来的数显然大于
n
n
n,我们不需要考虑
n
n
n的次数限制。
假设我们生成的序列上存在两个数
p
,
q
p,q
p,q,那么它们间生成的任何数都可以被表示为
i
p
+
j
q
ip+jq
ip+jq的形式,且
(
i
,
j
)
=
1
(i,j)=1
(i,j)=1。
于是对于序列上任意两个数之间会产生的数的数量我们是可以计算的,而Stern-Brocot树本身又可以像线段树一样查找,所以我们的目标就变成了在线段树上找出
[
l
,
r
]
[l,r]
[l,r]的区间。
由于要寻找这样的区间,我们自然就要计算当左右端点为
p
,
q
p,q
p,q时,它们之间
⩽
n
\leqslant n
⩽n的点的数量。
也就是求
i
p
+
j
q
⩽
n
ip+jq\leqslant n
ip+jq⩽n且
(
i
,
j
)
=
1
(i,j)=1
(i,j)=1的点对
(
i
,
j
)
(i,j)
(i,j)的数量。
这明显是可以通过莫比乌斯反演求出的。
我们设
i
p
+
j
q
⩽
k
ip+jq\leqslant k
ip+jq⩽k且
(
i
,
j
)
=
1
(i,j)=1
(i,j)=1的点对
(
i
,
j
)
(i,j)
(i,j)数量为
f
(
k
)
f(k)
f(k),而无需满足
(
i
,
j
)
=
1
(i,j)=1
(i,j)=1的点对数量为
F
(
k
)
F(k)
F(k)。
显然有
f
(
i
)
=
∑
j
∣
i
μ
(
j
)
F
(
i
j
)
=
∑
j
∣
i
μ
(
j
)
∑
k
=
1
⌊
i
j
⌋
⌊
i
j
−
k
p
q
⌋
f(i)=\sum_{j|i}\mu (j)F(\frac{i}{j})=\sum_{j|i}\mu (j)\sum_{k=1}^{\left\lfloor\frac{i}{j}\right\rfloor}\left\lfloor\frac{\frac{i}{j}-kp}{q}\right\rfloor
f(i)=j∣i∑μ(j)F(ji)=j∣i∑μ(j)k=1∑⌊ji⌋⌊qji−kp⌋
一次莫反时间复杂度为
O
(
n
ln
n
)
O\left(n\ln\,n\right)
O(nlnn)。
而我们线段树查询又会查询
l
o
g
L
log\,L
logL次,所以总时间复杂度为
O
(
n
ln
n
log
L
)
O\left(n\ln\,n\log\,L\right)
O(nlnnlogL)。
源码
代码挺好打的。
#include<bits/stdc++.h>
using namespace std;
#define MAXN 300005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;
const int INF=0x7f7f7f7f;
const int mo=1e9+7;
const int inv2=5e8+4;
const int jzm=2333;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
typedef pair<int,int> pii;
const double PI=acos(-1.0);
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
_T f=1;x=0;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
int gcd(int a,int b){return !b?a:gcd(b,a%b);}
int add(int x,int y){return x+y<mo?x+y:x+y-mo;}
int qkpow(int a,int s){int t=1;while(s){if(s&1)t=1ll*a*t%mo;a=1ll*a*a%mo;s>>=1;}return t;}
int a,b,n,mobius[MAXN],prime[MAXN],cntp;
LL L,R;bool oula[MAXN];
vector<int>ans;
void init(){
mobius[1]=1;
for(int i=2;i<=n;i++){
if(!oula[i])prime[++cntp]=i,mobius[i]=-1;
for(int j=1;j<=cntp&&1ll*prime[j]*i<=n;j++){
oula[i*prime[j]]=1;
if(i%prime[j]==0)break;
else mobius[i*prime[j]]=-mobius[i];
}
}
}
LL work(int p,int q){
LL res=0;
for(int i=1;p+q<=n/i;i++){
if(!mobius[i])continue;
for(int j=1;j*p+q<=n/i;j++)
res+=1ll*mobius[i]*(n/i-j*p)/(1ll*q);
}
return res;
}
void getAns(int x,int y){if(x+y<=n)getAns(x,x+y),ans.push_back(x+y),getAns(x+y,y);}
void sakura(int p,int q,LL al,LL ar,LL tot){
if(al==1&&ar==tot){getAns(p,q);ans.push_back(q);return ;}
if(p+q>n)return ;int mid=p+q;LL lsum=work(p,mid)+1LL;
if(al<=lsum)sakura(p,p+q,al,min(ar,lsum),lsum);
if(lsum<ar)sakura(p+q,q,max(1LL,al-lsum),ar-lsum,tot-lsum);
}
int main(){
read(a);read(b);read(n);read(L);read(R);init();
LL summ=work(a,b)+1LL;
if(L==1)ans.push_back(a),sakura(a,b,1LL,R-1LL,summ);
else sakura(a,b,L-1LL,R-1LL,summ);
for(int i=0;i<(int)ans.size();i++)printf("%d ",ans[i]);puts("");
return 0;
}