P6361 [CEOI 2018] Fibonacci representations 题解
P6361 [CEOI 2018] Fibonacci representations 题解
知识点
Fibonacci 数列,平衡树,齐肯多夫定理,连续段 DP,矩阵优化 DP,动态 DP。
分析
定义
-
\[\begin{matrix} b_1 & b_2 & \ldots & b_{k-1} & b_k & b_{k+1} & \ldots \\ 1 & 2 & \ldots & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \]
表示:
\[p = \sum_{i} b_i \cdot F_{i} \\ \] -
定义 \(F_{n+2}\) 的分裂:变为 \(F_{n} + F_{n+1}\)。
引入
我们从题目给定的条件考虑一下,很明显,有一种逐个插入的意味,那么我们就可以从这个角度来思考。
分裂
既然是逐个插入,就必定有最简单的只有一种的情况,我们先来看看这种:
它在过程中一直符合题目要求的前提(没有重复的斐波那契数)下可以如何分裂?
-
分裂一次:
\[\begin{matrix} 0 & 0 & \ldots & 1 & 1 & 0 & \ldots \\ 1 & 2 & \ldots & {k-2} & {k-1} & {k} & \ldots \\ \end{matrix} \]似乎没什么。
-
分裂两次:
\[\begin{matrix} 0 & 0 & \ldots & 1 & 1 & 0 & 1 & \ldots \\ 1 & 2 & \ldots & {k-4} & {k-3} & {k-2} & {k-1} & \ldots \\ \end{matrix} \]发现一个问题:如果我们把 \(k-1\) 对应的 \(1\) 去掉,就又变成了“分裂一次”的情况。
-
分裂 \(n\) 次……
所以我们可以得出结论:一个斐波那契数分裂过程中,在所有它分裂出来的数中,只有最小的那个可以再分裂。
那么这种情况的答案就很简单,为 \(\lfloor \frac{k-1}{2} \rfloor + 1\)。
去重
我们紧接着再看下一步,插入第二个,我们可以粗略分成两种:
-
插入的第二个与第一个不同:
\[\begin{matrix} 0 & 0 & \ldots & 0 & 1 & 0 & \ldots & 0 & 1 & 0 & \ldots \\ 1 & 2 & \ldots & {k-1} & k & {k+1} & \ldots & {m-1} & m & {m+1} & \ldots \\ \end{matrix} \]感觉这种仍旧是没什么……
-
插入的第二个与第一个相同:
\[\begin{matrix} 0 & 0 & \ldots & 0 & 2 & 0 & \ldots \\ 1 & 2 & \ldots & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \]似乎两个相同的不是很好,这样分解起来不如上面舒畅。
这种有相同的就不便于分解,所以我们尝试让它分裂:
考虑上面的性质,我们可以联想到:是不是对于任何正整数,都有一种把它表示成不同 Fibonacci 数的和方案呢?
答案是肯定的。
合并
分裂后发现还有两个可以合并,我们再把它们合起来,最终得到:
从这个现象,以及上一步中我们得到的结论,又可以进一步猜想:是不是对于任何正整数,都有一种把它表示成不同且互不相邻的斐波那契数的和的方案呢?
答案还是肯定的,这个就是齐肯多夫(Zeckendorf)定理的一部分,具体证明可以百度。
完整的齐肯多夫定理是:
任何正整数都可以表示成若干个不连续的斐波那契数(不包括第一个斐波那契数,即本题中不存在的那个 \(F_0\))之和。这种和式称为齐肯多夫表述法。
对于任何正整数,其齐肯多夫表述法都可以由贪心算法(即每次选出最大可能的斐波那契数)得到。
DP 设计
发现对于 \(p\),它的齐肯多夫表述法可以让我们对它求出本题答案:
- 设 \(p\) 的齐肯多夫表述法按升序排序后为 \(\set{Z_i}\),共有 \(m\) 项。
- 设 \(f_{i,0/1}\) 分别表示 \(Z_i\) 有没有分裂时的方案数,\(0\) 代表没有,\(1\) 代表有。
- 设 \(\Delta_i = Z_i-Z_{i-1}(i>1)\),特别地,\(i=1\) 时 \(\Delta_1 = Z_1\)。
那么就有初态和方程:
最终答案即为 \(f_{m,0} + f_{m,1}\)。
我们可以用矩阵表示:
(我们直接把这个塞到平衡树中就可以拿到子任务 3 和 5 的分数。)
动态维护
我们现在已经有了有静态的齐肯多夫表述法集合,考虑维护支持插入的动态的齐肯多夫表述法集合。
假设现在要在原齐肯多夫表述法集合 \(Z\) 插入第 \(k\) 个斐波那契数。
-
原集合中不包含第 \(k-1,k,k+1\) 个斐波那契数。
\[\begin{matrix} b_{1} & b_{2} & \ldots & 0 & 0 & 0 & \ldots \\ 1 & 2 & \ldots & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \]直接加入即可。
-
原集合中包含第 \(k+1\) 个斐波那契数。
\[\begin{matrix} b_{1} & b_{2} & \ldots & b_{k-1} & 0 & 1 & \ldots \\ 1 & 2 & \ldots & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \]那么我们不断往后合并
,总有一天会合并完的。 -
原集合中不包含第 \(k+1\) 个斐波那契数,但是包含第 \(k-1\) 个。
\[\begin{matrix} b_{1} & b_{2} & \ldots & 1 & 0 & 0 & \ldots \\ 1 & 2 & \ldots & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \]那么我们不断往后合并
,总有一天会合并完的。 -
原集合中包含第 \(k\) 个斐波那契数。
\[\begin{matrix} b_{1} & b_{2} & \ldots & 0 & 1 & 0 & \ldots \\ 1 & 2 & \ldots & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \]这种情况有点特殊,想象一下我们把第 \(k\) 个分裂成 \(k-1\) 和 \(k-2\) 后,发现原本就有一个 \(k-2\) 存在,这该怎么办?
设 \(q\) 满足以下条件:
- \(q \equiv k\pmod 2\)。
- \(\forall i \in [1,\frac{k-q}{2}],(k-2i)\in Z\)。
- \(q\) 是所有满足条件 1 和 2 的值中最小的那个。
又分两种情况:
-
\(q>2\):
会变成:
\[\begin{matrix} b_{1} & b_{2} & \ldots & 1 & 0 & 0 & 1 & \ldots & 1 & 0 & 1 & \ldots \\ 1 & 2 & \ldots & q-2 & q-1 & q & q+1 & \ldots & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \]但是这个并不能像上面一样暴力地修改……
考虑矩阵转移,发现其只由该值和上一个值的差值 \(\Delta\) 决定,而整个序列改变差值的或插入的最多只有 \(3\) 个,即:
- 插入一个 \(q-2\)。
- \(q(\to q+1)\) 的差值可能改变。
- \(k+1\) 后面的差值会改变。
-
\(q = 1\):我们把插入的 \(k\) 不断分裂,得到:
\[\begin{matrix} 2 & 1 & 1 & \ldots & 1 & 1 & 0 & \ldots \\ 1 & 2 & 3 & \ldots & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \]再从后面逐个合并,又会得到:
\[\begin{matrix} 0 & 1 & 0 & \ldots & 1 & 0 & 1 & \ldots \\ 1 & 2 & 3 & \ldots & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \]修改其实是一样修改差值的思路:
- \(1(\to 2)\) 的差值会改变。
- \(k+1\) 后面的差值会改变。
-
\(q = 2\):同理可得:
\[\begin{matrix} 1 & 0 & 1 & \ldots & 1 & 0 & 1 & \ldots \\ 1 & 2 & 3 & \ldots & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \]修改:
- 插入一个 \(1\)。
- \(k+1\) 后面的差值会改变。
如果原本还存在 \(k+2\) 的话,只要在此基础上再处理一下即可。
实现方式
用平衡树中维护一些差值为 \(2\) 的连续段即可实现动态维护。
中间的一些情况处理完之后会变成其他情况,所以我们可以用递归的形式实现。
那么转换成连续段后,每个转移矩阵的值需要重新定义:假设一个连续段与上一个中间有 \(\Delta\) 个 \(0\),共包含 \(c\) 个 \(1\),则矩阵变为:
或者按照实际意义也可推得。
复杂度
那么暴力合并的均摊次数也就是 \(O(n)\) 的,其他修改单次次数也较少,则复杂度为 \(O(nw^3\log_2{n})\),其中 \(w=2\)。
代码
//#define Plus_Cat "fib"
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define uint unsigned int
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define FOR(i,a,b) for(int i(a);i<=(int)(b);++i)
#define DOR(i,a,b) for(int i(a);i>=(int)(b);--i)
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define EDGE(g,i,x,y) for(int i=(g).h[(x)],y=(g)[(i)].v;~i;y=(g)[(i=(g)[i].nxt)>0?i:0].v)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);return Main();}signed Main
using namespace std;
constexpr int N(1e5+10);
namespace IOE {
//Fast IO...
} using namespace IOE;
namespace Modular {
//Modular
} using namespace Modular;
int n;
mt19937 rng(random_device {}());
struct Mat {
int d[4];
Mat(int val=0) { d[0]=d[3]=val,d[1]=d[2]=0; }
Mat(int d0,int d1,int d2,int d3) { d[0]=d0,d[1]=d1,d[2]=d2,d[3]=d3; }
Mat(int del,int cnt) {
int a(del>>1),b((del+1)>>1);
--cnt,d[0]=add(mul(cnt,a),1),d[1]=add(mul(cnt,b),1),d[2]=a,d[3]=b;
}
int &operator [](int i) { return d[i]; }
int const &operator [](int i) const { return d[i]; }
friend Mat operator *(const Mat &A,const Mat &B) {
return Mat(
(int)add(mul(A[0],B[0]),mul(A[1],B[2])),(int)add(mul(A[0],B[1]),mul(A[1],B[3])),
(int)add(mul(A[2],B[0]),mul(A[3],B[2])),(int)add(mul(A[2],B[1]),mul(A[3],B[3]))
);
}
};
struct Treap {
int rt,tot;
struct node {
int sta,cnt,lst;
Mat sel,pro;
int ls,rs;
uint blc;
node(int sta=0,int cnt=0,int lst=0,Mat sel=Mat(1),Mat pro=Mat(1),int ls=0,int rs=0,uint blc=rng()):
sta(sta),cnt(cnt),lst(lst),sel(sel),pro(pro),ls(ls),rs(rs),blc(blc) {}
void Update(int _lst) { lst=sta?sta-2+(cnt<<1):0,pro=sel=Mat(sta-_lst-1,cnt); }
} tr[N];
Treap():rt(0),tot(0) {}
#define ls(p) (tr[p].ls)
#define rs(p) (tr[p].rs)
void Init() { tr[rt=tot=1]=node(0,1,0); }
void Up(int p) {
tr[p].lst=(rs(p)?tr[rs(p)].lst:(tr[p].sta?tr[p].sta-2+(tr[p].cnt<<1):0));
tr[p].pro=tr[p].sel;
if(ls(p))tr[p].pro=tr[p].pro*tr[ls(p)].pro;
if(rs(p))tr[p].pro=tr[rs(p)].pro*tr[p].pro;
}
int Merge(int p,int q) {
if(!p||!q)return p|q;
if(tr[p].blc<=tr[q].blc)
return tr[p].lst=tr[q].lst,tr[p].pro=tr[q].pro*tr[p].pro,rs(p)=Merge(rs(p),q),p;
return tr[q].pro=tr[q].pro*tr[p].pro,ls(q)=Merge(p,ls(q)),q;
}
int Split(int &p,int key) { //[<=key] -> p,[>key] -> o
if(!p)return 0;
int o(0);
if(key<tr[p].sta)return o=Split(ls(p),key),o^=ls(p)^=o^=ls(p),p^=o^=p^=o,Up(o),o;
if(key==tr[p].sta)return o=rs(p),rs(p)=0,Up(p),o;
return o=Split(rs(p),key),Up(p),o;
}
int Split_begin(int &p) {
int o(0);
if(ls(p))return o=Split_begin(ls(p)),Up(p),o;
return o=p,p=rs(p),rs(o)=0,Up(o),o;
}
int Split_end(int &p) {
int o(0);
if(rs(p))return o=Split_end(rs(p)),Up(p),o;
return o=p,p=ls(p),ls(o)=0,Up(o),o;
}
int Cal() { return add(tr[rt].pro[0],tr[rt].pro[2]); }
void Link(int &p,int &q) {
int o(q);
q=Split(o,tr[p].sta+(tr[p].cnt<<1));
if(o)tr[p].cnt+=tr[o].cnt;
tr[p].Update(tr[rt].lst),rt=Merge(rt,p);
if(q)tr[o=Split_begin(q)].Update(tr[rt].lst),rt=Merge(rt,Merge(o,q));
}
void Insert(int key) {
int p(Split(rt,key)),q(Split(p,key+1));
if(p)return tr[p].sta=tr[p].lst+1,tr[p].cnt=1,Link(p,q);
if((rt==1&&!rs(rt))||key>tr[rt].lst+2)return tr[p=++tot].sta=key,tr[p].cnt=1,Link(p,q);
if(tr[p=Split_end(rt)].lst+2==key)return ++tr[p].cnt,Link(p,q);
if((key-tr[p].sta)&1) {
if(key!=tr[p].lst+1)
tr[p].cnt=(key+1-tr[p].sta)>>1,key=tr[p].lst+1,tr[p].Update(tr[rt].lst),rt=Merge(rt,p);
else if(++key,tr[p].cnt>1)--tr[p].cnt,tr[p].Update(tr[rt].lst),rt=Merge(rt,p);
return p=q,q=Split(p,key+1),(p?tr[p].sta=tr[p].lst+1:tr[p=++tot].sta=key),tr[p].cnt=1,Link(p,q);
}
if(key==tr[p].lst) {
if(tr[p].sta>2)return key=tr[p].sta-2,++tr[p].sta,Link(p,q),Insert(key);
if(tr[p].sta==1)return tr[p].sta=2,Link(p,q);
return tr[p].sta=1,++tr[p].cnt,Link(p,q);
}
int o(++tot);
tr[o].sta=tr[p].lst+1,tr[o].cnt=1,tr[p].cnt=(key-tr[p].sta)>>1;
if(tr[p].sta>2) {
key=tr[p].sta-2,++tr[p].sta;
if(tr[p].cnt)tr[p].Update(tr[rt].lst),rt=Merge(rt,p);
return Link(p=o,q),Insert(key);
}
if(tr[p].sta==2)return tr[p].sta=1,++tr[p].cnt,tr[p].Update(tr[rt].lst),rt=Merge(rt,p),Link(p=o,q);
tr[p].sta=2;
if(tr[p].cnt)return tr[p].Update(tr[rt].lst),rt=Merge(rt,p),Link(p=o,q);
return Link(p=o,q);
}
#undef ls
#undef rs
} trp;
signed main() {
#ifdef Plus_Cat
freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
I(n),trp.Init();
while(n--) {
int key;
I(key),trp.Insert(key),O(trp.Cal(),'\n');
}
return 0;
}

浙公网安备 33010602011771号