【AHOI2004】数字迷阵 [ 结论题 ]

来源:互联网 发布:深圳软件产业基地 英文 编辑:程序博客网 时间:2024/04/30 06:57

为什么说是结论题呢..因为我太弱了...


我根本就不知道什么叫斐波拉契最小展开啊..


好吧。题目如下。

小可可参观科学博物馆时,看到一件藏品,上面有密密麻麻的数字,如下所示: 1   2   3   5    8    13   21   34   55    89    144 ... 4   7   11  18   29   47   76   123  199   322   521 ... 6   10  16  26   42   63   110  178  288   466   754 ... 9   15  24  39   63   102  165  267  432   699   1131 ... 12  20  32  52   84   136  220  356  576   932   1508 ... 14  23  37  60   97   157  254  411  665   1076  1741 ... 17  28  45  73   118  191  309  500  809   1309  2118 ... 19  31  50  81   131  212  343  555  898   1453  2351 ... 22  36  58  94   152  246  398  644  1042  1686  2728 ... 25  41  66  107  173  280  453  733  1186  1919  3105 ... 27  44  71  115  186  301  487  788  1275  2063  3338 ... ... 仔细一分析,发现还挺有规律。 原来,第一行是Fibonacci数列。即,该行中除了第一个和第二个数分别为1和2之外,其他数都是其左侧相邻的两个数之和。 其后各行也类似于Fibonacci数列。只是第i行的第一个数是前 行中未出现的最小正整数,而其第二个数与该行第一个数以及所在行的编号相关,即A[i,2]=A[i,1]*2-(i-1) 。如在第一行中未出现的最小正整数为4,前三行中未出现的最小正整数为9。故第二行以4和7开头,而第四行以9和15开头。 小可可高兴地把这个发现告诉了爷爷。爷爷问道:你能否一口报出第i行、第j列的那个数对m取模的结果是多少呢? 聪明的小可可通过心算就能知道答案。你是否能编写程序求解呢? 输入:每行有三个分别用一个空格隔开的正整数,分别是i、j和m。 其中,i, j<=1000000000 ,2<=m<=10000 。 输出:每行输出对应的第i行、第j列的那个正整数对m取模的结果。 样例: (1) 输入(matrix.in):     1 2 99 输出(matrix.out):     2 (2) 输入(matrix.in):    9 1 999 输出(matrix.out):     22 



初看此题,很容易看出实际上求出每行第一个就行了。

然后想到找规律,将第一排的数做差,得到差序列

3 2 3 3 2 3 2 3 3 2 3 3 2 3 2 3 3 2 3 ..

好像有点规律!我们再玩一玩(我不会说我是被人点醒的..)

3

2 3

3 2 3

2 3 3 2 3

3 2 3 2 3 3 2 3 


发现了什么?

把差序列分解成1,2,3,5,8这样的长度为斐波拉契中对应数字的段

下面的段是由上面的段拼出来的。


实现很猥琐..此为方法1


于是请教LYP君,得利器【Fibonacci的最小拆分】

对于n,每次选取Fibonacci中小于n的最大的那一项,将n减去之,递归进行。

最后,对于Fibonacci中的每一项,减去了就记为1否则为零

得到一个01序列。

将(行号-1)进行拆分,在拆分的序列后加上01

如 第5行,拆分(5 - 1) 即4,

4 = 3 + 1

序列:

Fibonacci 8 5 3 2 1

最后得到   0 0 1 0 1

加上01

0 0 1 0  1 0 1

带入

Fibonacci 8 5 3 2 1

      新序列 1 0 1 0 1 = 8 + 3 + 1 = 12

发现了么?就是第四行的第一个。

并且还有一个性质,第 i 行第 j 个是在i - 1的证书拆分后加 0 1 再加 (j - 1 )个0

为什么? 说实话第二个性质我也不知道怎么证明。但是事实就是这样。

那么对第一个性质,由于每行第一个数拆分的最后两项,必须是  0 1

然后假设拆分是[ k ] 0 1

假设上一行的是[ k' ] 0 1

则 k 应该是 值 大于 k‘ 的最小的哪一个。

怎么得到呢?

实际上所有 [ k ] 的值可以转化为0、1、2、3、....的序列。

所以说[ k ] 就是 0 、 1 、 2、 3 的拆分。此为方法2


其实第一项有一个公式


A[ x , 1 ] = x - 1 + trunc( x * (( 1 + sqrt(5) ) / 2));


通过上面的说明及fibonacci的性质,显然 trunc( x * (( 1 + sqrt(5) ) / 2)) 这一段的值近似等于在拆分后加一个0转化回来的值

然后再加上x, 实际上是加上x的拆分,然后得到了在x拆分后加两个0再转化回来的值。

那为什么最后 - 1而不是+ 1 呢?

说实话我也不知道.....MD..或许是取整的误差、后推的误差什么的吧。


但这也太巧了吧!!!


但是真的无法证明。内心领会吧。此为方法3



被虐了..



提供非公式版的代码(就是方法2的代码)

#include<iostream>#include<cstdio> #include<cstdlib>#include<cstring>#include<cmath>#include<algorithm>#ifdef WIN32#define ot "%I64d"#else#define ot "%lld"#endif#define max(a, b, t) ({t _ = (a), __ = (b); _ > __ ? _ : __;})#define min(a, b, t) ({t _ = (a), __ = (b); _ < __ ? _ : __;})using namespace std;struct mtx {int a[2][2];} ans, t;int x, y, mo;int c[47] = {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, 701408733, 1134903170,};mtx operator * (mtx x, mtx y){mtx z;for (int i = 0; i <= 1; i++)for (int j = 0; j <= 1; j++){z.a[i][j] = 0;for (int k = 0; k <= 1; k++){z.a[i][j] += x.a[i][k]* y.a[k][j];z.a[i][j] %= mo;}}return z;}void qp(int k){t = ans;--k;while(k){if (k & 1) ans = ans * t;t = t * t; k >>= 1;}}void init(){freopen("matrix.in", "r", stdin);freopen("matrix.out", "w", stdout);scanf("%d%d%d", &x, &y, &mo);}int main(){init();int t = 44, l = 1, r = 1 - x;--x;while (x){while (c[t] > x) --t;x -= c[t]; l += ((c[t] % mo) << 1) + c[t - 1] % mo; l %= mo;}r += (l * 2);while (r < 0) r += mo;r %= mo;ans.a[0][0] = 0;ans.a[0][1] = 1;ans.a[1][0] = 1;ans.a[1][1] = 1;if (y == 1) printf("%d", l); else if (y == 2) printf("%d", r);else {qp(y - 2); //quickpowerint s = l * ans.a[0][1] + r * ans.a[1][1];s %= mo;printf("%d", s);}   return 0;}


还有一个方法一的Code(CCL大神提供, 不得不说缩进 is so beautiful ,我还改了好久...还是这样,懒得改了)

type arr  =  array[1  ..  2, 1 .. 2]of int64;var i, j, k, l, x, y, z, xx : longint;    a, b : array[ - 1 .. 100]of int64;    ans, d, pp : arr;    sum : int64;operator  * (a, b : arr)c : arr;var i, j : longint;begin  for i := 1 to 2 do   for j := 1 to 2 do c[i, j] := 0;  for i := 1 to 2 do    for j := 1 to 2 do      for k := 1 to 2 do      c[i, j] := (c[i, j] + a[i, k] * b[k, j]) mod z;end;function qpow(x : longint) : arr;var d : arr;begin  if x = 1 then exit(pp);  if x = 2 then exit(pp * pp);  d := qpow(x div 2);  d := d * d;  if x mod 2 = 1 then d := d * pp;  qpow := d;end;begin  assign(input, 'matrix.in');reset(input);    readln(x, y, z); xx := x;    close(Input); a[0] := 1; a[1] := 1;a[2] := 2; x := x - 1;  for i := 3 to 60 do    a[i] := a[i - 1] + a[i - 2];  b[1] := 3;b[2] := 5;  for i := 3 to 60 do    b[i] := b[i - 1] + b[i - 2];  i := 0;j := 0;  while j< = x do begin i := i + 1;j := j + a[i];sum := sum + b[i]; end;  j := j - a[i];  x := x - j; sum := (sum - b[i]);  while i>0 do begin    if i = 2 then begin if x = 1 then sum := sum + 2; if x = 2 then sum := sum + 5;      break;      end;    if i = 1 then begin if x = 1 then sum := sum + 3; break; end;    if x> = a[i - 2] then begin sum := sum + b[i - 2]; x := x - a[i - 2];end else i := i - 1;    i := i - 1;  end;  ans[1, 1] := (sum + 1) ;  ans[1, 2] := ans[1, 1] * 2 - (xx - 1);  pp[1, 1] := 0;pp[1, 2] := 1;pp[2, 1] := 1;pp[2, 2] := 1;  if y> = 2 then begin  d := qpow(y - 1);  ans := ans * d;         end;  assign(Output, 'matrix.out');rewrite(output);  writeln(ans[1, 1] mod z);  close(output);end.



原创粉丝点击