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 & 0 & 1 & 0 & \ldots \\ 1 & 2 & \ldots & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \]

它在过程中一直符合题目要求的前提(没有重复的斐波那契数)下可以如何分裂?

  • 分裂一次:

    \[\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} \]

    似乎两个相同的不是很好,这样分解起来不如上面舒畅。

这种有相同的就不便于分解,所以我们尝试让它分裂:

\[\ \begin{matrix} 0 & 0 & \ldots & 0 & 2 & 0 & \ldots \\ 1 & 2 & \ldots & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \\ \Downarrow \\ \begin{matrix} 0 & 0 & \ldots & 1 & 1 & 1 & 0 & \ldots \\ 1 & 2 & \ldots & {k-2} & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \\ \]

考虑上面的性质,我们可以联想到:是不是对于任何正整数,都有一种把它表示成不同 Fibonacci 数的和方案呢?

答案是肯定的。

合并

分裂后发现还有两个可以合并,我们再把它们合起来,最终得到:

\[\begin{matrix} 0 & 0 & \ldots & 1 & 0 & 0 & 1 & \ldots \\ 1 & 2 & \ldots & {k-2} & {k-1} & k & {k+1} & \ldots \\ \end{matrix} \\ \]

从这个现象,以及上一步中我们得到的结论,又可以进一步猜想:是不是对于任何正整数,都有一种把它表示成不同且互不相邻的斐波那契数的和的方案呢?

答案还是肯定的,这个就是齐肯多夫(Zeckendorf)定理的一部分,具体证明可以百度。

完整的齐肯多夫定理是:

任何正整数都可以表示成若干个不连续的斐波那契数(不包括第一个斐波那契数,即本题中不存在的那个 \(F_0\))之和。这种和式称为齐肯多夫表述法

对于任何正整数,其齐肯多夫表述法都可以由贪心算法(即每次选出最大可能的斐波那契数)得到。

——齐肯多夫定理_百度百科 (baidu.com)

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_{0,0} = 1,f_{0,1} = 0 \\ \begin{cases} f_{i,0} = f_{i-1,0} + f_{i-1,1} \\ f_{i,1} = \lfloor \frac{\Delta_i - 1}{2} \rfloor f_{i-1,0} + \lfloor \frac{\Delta_i}{2} \rfloor f_{i-1,1} \\ \end{cases} \]

最终答案即为 \(f_{m,0} + f_{m,1}\)

我们可以用矩阵表示:

\[\begin{bmatrix} 1 & 1 \\ \lfloor \frac{\Delta_i - 1}{2} \rfloor& \lfloor \frac{\Delta_i}{2} \rfloor\\ \end{bmatrix} \begin{bmatrix} f_{i-1,0} \\ f_{i-1,1} \\ \end{bmatrix} = \begin{bmatrix} f_{i,0} \\ f_{i,1} \\ \end{bmatrix} \]

(我们直接把这个塞到平衡树中就可以拿到子任务 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\) 满足以下条件:

    1. \(q \equiv k\pmod 2\)
    2. \(\forall i \in [1,\frac{k-q}{2}],(k-2i)\in Z\)
    3. \(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\) 个,即:

      1. 插入一个 \(q-2\)
      2. \(q(\to q+1)\) 的差值可能改变。
      3. \(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. \(1(\to 2)\) 的差值会改变。
      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. 插入一个 \(1\)
      2. \(k+1\) 后面的差值会改变。

    如果原本还存在 \(k+2\) 的话,只要在此基础上再处理一下即可。

实现方式

用平衡树中维护一些差值为 \(2\) 的连续段即可实现动态维护。

中间的一些情况处理完之后会变成其他情况,所以我们可以用递归的形式实现。

那么转换成连续段后,每个转移矩阵的值需要重新定义:假设一个连续段与上一个中间有 \(\Delta\)\(0\),共包含 \(c\)\(1\),则矩阵变为:

\[\ \begin{bmatrix} 1 & 1 \\ 0 & 1 \\ \end{bmatrix}^{c-1} \begin{bmatrix} 1 & 1 \\ \lfloor \frac{\Delta}{2} \rfloor & \lfloor \frac{\Delta + 1}{2} \rfloor\\ \end{bmatrix} \\ = \begin{bmatrix} 1 & c-1 \\ 0 & 1 \\ \end{bmatrix} \begin{bmatrix} 1 & 1 \\ \lfloor \frac{\Delta}{2} \rfloor & \lfloor \frac{\Delta + 1}{2} \rfloor\\ \end{bmatrix} \\ = \begin{bmatrix} (c-1)\lfloor \frac{\Delta}{2} \rfloor+1 & (c-1)\lfloor \frac{\Delta + 1}{2} \rfloor+1 \\ \lfloor \frac{\Delta}{2} \rfloor & \lfloor \frac{\Delta + 1}{2} \rfloor\\ \end{bmatrix} \]

或者按照实际意义也可推得。

复杂度

那么暴力合并的均摊次数也就是 \(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;
}

posted @ 2025-05-30 22:13  Add_Catalyst  阅读(9)  评论(0)    收藏  举报