【题解】Well-known Numbers

题目信息

题目来源:Codeforces Round #139 (Div. 2);

在线评测地址:CF#225B

运行限制:(2.00 extrm{s}/256 extrm{MiB})

题目描述

Numbers (k)-bonacci ((k) is integer, (k > 1)) are a generalization of Fibonacci numbers and are determined as follows:

定义 (k) 阶斐波那契数列如下:

  • (F(k, n) = 0), for integer (n), (1 ≤ n < k);
  • (F(k, k) = 1);
  • (F(k, n) = F(k, n - 1) + F(k, n - 2) + ... + F(k, n - k)), for integer (n), (n > k).

[F(k,n)=egin{cases} 0&1le n<k\ 1&n=k\ sumlimits_{i=1}^k F(k,n-i)&n>kend{cases} ]

Note that we determine the k-bonacci numbers, F(k, n), only for integer values of n and k.

(F(k,n)) 只对整数定义(或者严格来说是正整数)。

You've got a number (s), represent it as a sum of several (at least two) distinct (k)-bonacci numbers.

你得到了一个数 (s),试用若干个互异的 (k) 阶斐波那契数的和表示。数据保证存在解。

输入格式

一行,(s)(t)

输出格式

第一行一个正整数 (m),表示所用来表示和的数的个数。

下一行 (m) 个正整数,表示数。每个数都应该是 (k) 阶斐波那契数,且和为 (s)

数据规模与约定

(1le s,kle 10^9)(k>1)

分析

给出一个数列的定义,要求用这个数列的某些互异的项之和来表示某个数。

首先,注意到这个数列的定义,以及这个数列是单调的,我们不妨考虑一下贪心。即:自后往前选,选到可以选进去的就选进去。

对于这个做法,我们要考虑这么几个问题:

  1. 这个做法是否一定存在可行解?
  2. 这个做法能否满足互异和至少两个的要求?

首先是可行性的问题。根据事后诸葛亮可以推断确实是可行的。我们可以先证明几个引理:

( exttt{Lemma 1})(F(k,n+1)le 2F(k,n))

由定义显然可证。

( exttt{Lemma 2})(F(k,k+t)=2^{t-1}(1le tle k))

根据定义,简单推导可得。

( exttt{Theorem}):对于任意 (s,k),使用贪心算法总有解。

  • (1le s<2^k) 时,根据 ( exttt{Lemma 2}) 可得一定有解;
  • (sge 2^k) 时,不妨设 (F(k,s^prime) le s<F(k,s^prime+1)),此时利用贪心算法得到的下一步必然是 (s-F(k,s^prime))

根据 ( exttt{Lemma 1})(s-F(k,s^prime)<F(k,s^prime+1)-F(k,s^prime)le F(k,s^prime)),即可以不使用 (F(k,s^prime))(或更大)来表示 (s-F(k,s^prime))

综上,根据数学归纳法得证。


接下来就是一些有的没的的构造了。

对于互异而言是肯定不可能的,由定义易得。然而我们并不保证只有一个数的情况。

这个时候,我们可以加一个 (0),这样就能水过去了(迫真)。

注意事项

  • 注意是否对一个元素的情况特判;
  • 注意是否有特殊处理前 (k) 个元素,同时注意元素的值域边界。

Code

#include <cstdio>
#include <vector>
using namespace std;

typedef long long ll;
vector<ll> vt, ans;

int main()
{
	int s, k;
	ll t = 1;

	scanf("%d%d", &s, &k);
	
	vt.push_back(1);
	for (int i = 1; i < k && t <= s; i++, t <<= 1) // 特殊处理前 k 项,注意每一项都 <= s
		vt.push_back(t);
	
	while (true)
	{
		t = 0;
		for (int j = 0; j < k && j < vt.size(); j++) // 用定义推后面的项
			t += vt[vt.size()-j-1];
		if (t > s) // 利用单调性
			break;
		vt.push_back(t);
	}

	for (int i = vt.size() - 1; i >= 0; i--) // 贪心
		if (vt[i] <= s)
		{
			s -= vt[i];
			ans.push_back(vt[i]);
		}
	
	if (ans.size() > 1)
	{
		printf("%d
", ans.size());
		for (int i = 0; i < ans.size(); i++)
			printf("%lld ", ans[i]);
	}
	else
		printf("2
%lld 0
", ans[0]); // 大小等于 0 时特判

	return 0; // 然后就 AC 了、
}
原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-cf225b.html