user

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
一个不少,两个正好
不断抛掷一枚标准硬币,直到连续两次抛出正面,记此时已经抛掷的次数为M。令P(n)表示M被n整除的概率。比方说,在计算P(2)时,如“正正”、“正反正正”或是“反正反反正正”这样的抛掷结果都要算入,而“反正正”或“正反反正正”就不算。

已知P(2)=\frac 3 5 和 P(3)=\frac{9}{31} 。实际上,可以证明P(n)总是有理数。

对于素数p和最简分数a/b,记 Q(\frac{a}{b},p) 为满足a\equiv bq\pmod p 的最小正整数q。例如,Q(P(2),109)=Q(\frac{3}{5},109)=66, 这是因为5\cdot66=330\equiv 3 \pmod{109}且66是满足上式的最小正整数;同理可得Q(P(3),109)=46。

求Q(P(10^{18}),1\ 000\ 000\ 009)。


我的思路:首先计算P(10**18).对于长度为n(n是一个偶数)的末尾为两个连续正而且前面不出现两个连续正的,可以这么思考:
其实只有两种结果,一个是反,另一个是正反,两种共同铺满长度为 n-2的结果,然后按照长度除掉 2^n.
这么加起来算一算结果式子即可.

然后考虑计算Q函数:这不就是模意义下的逆元吗...

请给我cpp实现.

deepseek 思考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
我们被问到:

"一个不少,两个正好
不断抛掷一枚标准硬币,直到连续两次抛出正面,记此时已经抛掷的次数为M。令P(n)表示M被n整除的概率。比方说,在计算P(2)时,如“正正”、“正反正正”或是“反正反反正正”这样的抛掷结果都要算入,而“反正正”或“正反反正正”就不算。

已知P(2)=3/5 和 P(3)=9/31。实际上,可以证明P(n)总是有理数。

对于素数p和最简分数a/b,记 Q(a/b, p) 为满足 a ≡ b q (mod p) 的最小正整数q。例如,Q(P(2),109)=Q(3/5,109)=66,因为5*66=330≡3(mod 109)且66是满足的最小正整数;同理可得Q(P(3),109)=46。

求Q(P(10^18), 1 000 000 009)。

我的思路:首先计算P(10**18).对于长度为n(n是一个偶数)的末尾为两个连续正而且前面不出现两个连续正的,可以这么思考:
其实只有两种结果,一个是反,另一个是正反,两种共同铺满长度为 n-2的结果,然后按照长度除掉 2^n.
这么加起来算一算结果式子即可.

然后考虑计算Q函数:这不就是模意义下的逆元吗...

请给我cpp实现."

我们需要计算P(n),其中n = 10^18,然后对于素数p = 1000000009(注意:1 000 000 009 = 10^9+9,是素数吗?是的,1000000009是已知素数,通常用于模运算)。然后给定P(n) = a/b为最简分数,求最小正整数q使得 a ≡ b q (mod p),即 q ≡ a * b^{-1} (mod p),并且 q 在 1 到 p-1 之间(最小正整数)。因为p是素数,且b不是p的倍数,这等价于模p下的除法。

所以任务是:

推导P(n)的公式。

对于n=10^18,计算P(n)的有理数表示,或者直接计算a * b^{-1} mod p,其中p=1000000009。我们可能不需要求出有理数,而是求出模p下的值。P(n)可以表示为模p下的有理数吗?因为概率是有理数,我们需要模p下的a * b^{-1} mod p。如果我们可以直接计算概率在模p下的值(即a * b^{-1} mod p),那么q就是这个值(如果值介于1和p-1之间)。但注意:q是最小正整数满足同余式,所以如果算出a*b^{-1} mod p得到的是0?概率不会是0吧。算出的就是q。

我们需要推导P(n)的确切表达式。题意:M是直到连续两次正面的抛掷次数。求P(n) = P(M能被n整除)。

已知P(2)=3/5, P(3)=9/31。我们需要一般公式。M的分布是著名的:等待时间直到出现"HH"(正面正面)的分布。其概率生成函数或质量函数已知。
设抛掷硬币直到出现连续两次正面。抛掷次数为M。M的概率质量函数为:
P(M = k) = F_{k-1} / 2^k 对于k>=2?实际上,经典的抛硬币直到连续两次正面的次数分布:P(M=k) = Fib(k-1) / 2^k,其中Fib(1)=1, Fib(2)=1, Fib(3)=2等?让我们验证。
对于k=2: M=2对应“正正”。概率=1/4。Fib(1)=1, 1/4=1/4。正确。
k=3: M=3对应“反正正”,概率=1/8。Fib(2)=1, 1/8。正确。
k=4: 序列末尾为“正反正正”或“反反正正”?实际上长度为4且以HH结尾且之前没有HH的序列:只能以T开头,然后H,H?或者T,T,H,H?让我们列出:TTHH, HTHH?HTHH在第三个是H?序列:H,T,H,H。但前三个包含HH吗?HTH没有HH。T,H,H?THH有HH!等等,我们要求第一次出现连续两次正面是在最后两次。所以序列不能提前出现HH。对于k=4:可能序列:
T T H H (概率1/16)
H T H H (概率1/16)
T H H H?但前三个有HH(第二、三位是HH),所以不行。
所以有两个序列,概率2/16=1/8。而Fib(3)=2,所以P(M=4)=2/16=1/8。正确。
所以P(M=k) = F_{k-1} / 2^k,其中F_1=1, F_2=1, F_3=2, F_4=3, F_5=5,... 即斐波那契数列。
验证k=5: Fib(4)=3,P(M=5)=3/32。正确。
所以M的分布是P(M=k) = F_{k-1}/2^k for k>=2,且sum_{k=2}^\infty F_{k-1}/2^k = 1? 已知 sum F_k x^k = x/(1-x-x^2)。令x=1/2,sum_{k=1} F_k /2^k = (1/2) / (1 - 1/2 - 1/4) = (1/2)/(1/4)=2。所以 sum_{k=2} F_{k-1}/2^k = sum_{m=1} F_m /2^{m+1} = 1/2 * 2 =1。正确。

因此P(n) = P(M ≡ 0 mod n) = sum_{m=1}^\infty P(M = m n)?注意M的最小值是2,所以m n >=2。因此
P(n) = sum_{m=1}^\infty F_{m n - 1} / 2^{m n}.

对于给定的n,我们需要求这个无限级数。通常这涉及斐波那契数的生成函数。我们可以用斐波那契数的Binet公式或者利用模周期性?这里n=10^18,非常大,直接求和不可能。我们需要P(n)的闭式或简化。

观察:P(M = k) = F_{k-1}/2^k。所以P(n) = sum_{j=0}^\infty F_{n j + n - 1} / 2^{n j + n},其中j从0开始?令m = j+1,m从1到∞:P(n) = sum_{m=1}^\infty F_{m n -1} /2^{m n}.
已知 sum_{k=1}^\infty F_k x^k = x/(1-x-x^2)。而我们需要提取指数模n同余于-1的那些项。这可以使用选择根(roots of unity filter):
P(n) = sum_{k=2, k≡0 mod n} F_{k-1}/2^k = sum_{t=0}^{n-1} ... 使用单位根。
但注意我们要求k是n的倍数,即k ≡ 0 (mod n)。所以k-1 ≡ -1 (mod n)。所以我们可以写出:
P(n) = (1/n) * sum_{j=0}^{n-1} ω^{-j(-1)} ? 稍等。

设S = sum_{k=1}^\infty F_k x^k = x/(1-x-x^2),其中x = 1/2。但我们要的是k = m n - 1,m>=1。所以 sum_{m=1}^\infty F_{mn-1} x^{mn} = x * sum_{m=1}^\infty F_{mn-1} x^{mn-1} = x * T,其中T = sum_{r, r ≡ -1 mod n} F_r x^r。可以用单位根提取:
T = (1/n) sum_{j=0}^{n-1} ω^{j} S(ω^{-j} x)?让我们仔细推导。

设 S(z) = sum_{k=1}^\infty F_k z^k = z/(1-z-z^2)。我们需要 sum_{r≡-1 (mod n)} F_r z^r = (1/n) sum_{j=0}^{n-1} ω^{j(-1)?} S(ω^j z)? 实际上,标准筛选:sum_{r≡a mod n} a_r = (1/n) sum_{j=0}^{n-1} ω^{-j a} sum_r a_r ω^{j r}。这里a_r = F_r z^r,a = -1 mod n。所以:
sum_{r≡-1} F_r z^r = (1/n) sum_{j=0}^{n-1} ω^{j} S(ω^j z)。因为 ω^{-j*(-1)} = ω^j。
所以 T = (1/n) sum_{j=0}^{n-1} ω^j S(ω^j z).
那么 P(n) = sum_{m=1} F_{mn-1} z^{mn} = z * T = z * (1/n) sum_{j=0}^{n-1} ω^j S(ω^j z),其中z=1/2。
故 P(n) = (1/n) sum_{j=0}^{n-1} ω^j * (ω^j z) / (1 - ω^j z - (ω^j z)^2) = (1/n) sum_{j=0}^{n-1} ω^{2j} z / (1 - ω^j z - ω^{2j} z^2). (注意 z 是 1/2)
简化:P(n) = (1/n) sum_{j=0}^{n-1} ω^{2j} z / (1 - ω^j z - ω^{2j} z^2).
其中z=1/2,ω = exp(2πi/n) 是n次本原单位根。

这个表达式对于任何n都成立。我们需要对于n=10^18,在模p=1000000009下计算P(n)。但是n太大了,循环求和有10^18项,不可能直接求。但也许由于斐波那契数的模周期性,或者这个和可以进一步化简为涉及斐波那契数和卢卡斯数的闭式。

我们知道 sum_{k=1}^\infty F_{kn} x^{kn} 可以用斐波那契多项式的公式?实际上,已知:
sum_{m=0}^\infty F_{a m + b} z^{a m + b} 有闭式,也许通过解线性递推。
由于M的分布服从递推?M的生成函数是 E(z^M) = z^2 / (4 - 2z - z^2)?我们来检查一下。
之前我们有P(M=k) = F_{k-1}/2^k。所以概率生成函数:G(z) = sum_{k=2}^\infty P(M=k) z^k = sum_{k=2} F_{k-1} (z/2)^k = (z/2)^2 sum_{m=1} F_m (z/2)^m = (z^2/4) * (z/2) / (1 - z/2 - z^2/4) = (z^3/8) / ((4 - 2z - z^2)/4) = z^3 / (2(4 - 2z - z^2))? 再算一下:
sum_{m=1} F_m y^m = y/(1-y-y^2). 令y = z/2. 则 sum_{m=1} F_m (z/2)^m = (z/2) / (1 - z/2 - z^2/4) = (z/2) / ((4 - 2z - z^2)/4) = 2z / (4 - 2z - z^2).
所以 G(z) = (z/2)^2 * 2z/(4-2z-z^2) = (z^2/4)(2z/(4-2z-z^2)) = z^3/(2(4-2z-z^2)).
我们来验证:对于P(2)是否可以得到3/5?P(2) = P(M 是偶数)?不对,P(2)是M被2整除的概率,即所有偶数k的概率和。
P(M是偶数) = (G(1)+G(-1))/2.
G(1) = 1/(2(4-2-1)) = 1/2.
G(-1) = (-1)/(2(4+2-1)) = -1/(2*5) = -1/10.
所以 P(2) = (1/2 - 1/10)/2? 等等,(G(1)+G(-1))/2 = (1/2 - 1/10)/2 = (5/10 - 1/10)/2 = (4/10)/2 = 4/20 = 1/5. 但已知P(2)=3/5!矛盾。

哪里出错了?
我们检查:G(z) = sum_{k=2} P(M=k) z^k. P(M=2)=1/4, M=3=1/8, M=4=2/16=1/8, ...
G(z) = 1/4 z^2 + 1/8 z^3 + 1/8 z^4 + 3/32 z^5 + ...
代入z=1: sum P = 1/4+1/8+1/8+... =1 正确。
如果 G(z) = z^3/(2(4-2z-z^2)),我们算 G(1) = 1/(2*(1)) = 1/2. 但是G(1)必须是1!因为所有概率和为1。所以我们的G(z)公式错了,因为G(1)应该等于1。
重新计算:sum_{k=2} F_{k-1} (z/2)^k. 令 m = k-1, 则 m从1开始,k=m+1. sum_{m=1} F_m (z/2)^{m+1} = (z/2) sum_{m=1} F_m (z/2)^m = (z/2) * [ (z/2)/(1 - z/2 - (z/2)^2) ] = (z^2/4) / (1 - z/2 - z^2/4) = (z^2/4) / ((4 - 2z - z^2)/4) = z^2 / (4 - 2z - z^2).
验证:z=1: 1/(4-2-1)=1. 正确。所以 G(z) = z^2 / (4 - 2z - z^2).
那么 P(M是偶数) = (G(1)+G(-1))/2. G(1)=1. G(-1) = 1/(4 +2 -1) = 1/5. 所以 (1+1/5)/2 = 6/10 = 3/5. 正确!P(2)=3/5. 好的。
对于P(3),即M被3整除。概率为 (G(1)+G(ω)+G(ω^2))/3,其中ω=exp(2πi/3). 已知P(3)=9/31。

所以我们有一个明确的生成函数 G(z) = z^2 / (4 - 2z - z^2)。
那么 P(n) = sum_{k≡0 mod n} P(M=k) = (1/n) sum_{j=0}^{n-1} G(ω^j) 其中 ω = exp(2πi/n).
所以 P(n) = (1/n) sum_{j=0}^{n-1} ω^{2j} / (4 - 2ω^j - ω^{2j}).
这是我们之前的结果(之前我们有z=1/2之类,现在直接用生成函数更方便)。

现在我们需要用闭式表示 P(n)。可以利用分式分解。
分母 4 - 2z - z^2 = -(z^2 + 2z - 4)。其根为 z = (-2 ± sqrt(4 + 16))/2 = -1 ± sqrt(5)。设 φ = (1+sqrt(5))/2 ≈ 1.618,则 -1 - sqrt(5) = -2φ?等等。sqrt(5) = 2φ - 1? 实际上 φ = (1+√5)/2, 所以 √5 = 2φ - 1。
那么根: r1 = -1 + √5 = -1 + 2φ - 1 = 2φ - 2 = 2(φ-1) = 2/φ,因为 φ-1 = 1/φ。
r2 = -1 - √5 = -1 - (2φ-1) = -2φ。
所以分母 = -(z - r1)(z - r2) = (r1 - z)(r2 - z)?不,4 - 2z - z^2 = (r1 - z)(z - r2)? 还是用部分分式。
G(z) = z^2 / (4 - 2z - z^2) = -z^2 / (z^2 + 2z - 4) = -z^2 / ((z - r1)(z - r2)).
做部分分式:z^2 / ((z - r1)(z - r2)) = A + B/(z - r1) + C/(z - r2)? 因为分子次数等于分母,先做多项式除法:
z^2 / (z^2+2z-4) = 1 - (2z-4)/(z^2+2z-4). 所以 -z^2/(z^2+2z-4) = -1 + (2z-4)/(z^2+2z-4). 然后分解 (2z-4)/((z-r1)(z-r2)) = D/(z-r1) + E/(z-r2).
我们有 r1 = 2/φ, r2 = -2φ.
那么 D = (2r1-4)/(r1-r2), E = (2r2-4)/(r2-r1).
因为 r1 - r2 = 2/φ + 2φ = 2(φ + 1/φ) = 2√5. (因为 φ + 1/φ = √5? 等一下:φ = (1+√5)/2, 1/φ = (√5-1)/2, 和 = √5。对)。
计算:2r1-4 = 4/φ - 4 = 4(1-φ)/φ = -4(φ-1)/φ = -4/φ^2,因为 φ-1 = 1/φ.
2r2-4 = -4φ - 4 = -4(φ+1). 注意 φ+1 = φ^2.
所以 D = (-4/φ^2) / (2√5) = -2/(φ^2 √5).
E = (-4φ^2) / (-2√5) = 2φ^2 / √5.
因此 G(z) = -1 + D/(z - r1) + E/(z - r2) = -1 - 2/(φ^2 √5) * 1/(z - 2/φ) + 2φ^2/√5 * 1/(z + 2φ).

实际上我们可以写 G(z) = -1 + sum_{j} ... 但P(n) = (1/n) sum_{j=0}^{n-1} G(ω^j).
注意到 sum_{j=0}^{n-1} 1 = n,所以 -1 的部分贡献为 -1。
所以 P(n) = -1 + (1/n) sum_{j=0}^{n-1} [ D/(ω^j - r1) + E/(ω^j - r2) ].

我们需要计算 sum_{j=0}^{n-1} 1/(ω^j - r) ,其中 r 是常数。这是经典的和式,可以用公式:
sum_{j=0}^{n-1} 1/(ω^j - r) = n r^{n-1}/(r^n - 1)?我们来验证。
考虑多项式 z^n - 1 = prod_{j=0}^{n-1} (z - ω^j). 对数导数: (n z^{n-1})/(z^n - 1) = sum_{j=0}^{n-1} 1/(z - ω^j). 令 z = r,我们有 sum 1/(r - ω^j) = n r^{n-1}/(r^n - 1). 所以 sum 1/(ω^j - r) = - n r^{n-1}/(r^n - 1). 因此:
(1/n) sum 1/(ω^j - r) = - r^{n-1}/(r^n - 1) = 1/(1 - r^n) * r^{n-1}? 实际上是 -r^{n-1}/(r^n-1) = r^{n-1}/(1 - r^n). 是的。

因此:
(1/n) sum_{j} D/(ω^j - r1) = D * r1^{n-1} / (1 - r1^n).
(1/n) sum_{j} E/(ω^j - r2) = E * r2^{n-1} / (1 - r2^n).

所以 P(n) = -1 + D * r1^{n-1}/(1 - r1^n) + E * r2^{n-1}/(1 - r2^n).

现在代入 D, E, r1, r2:
r1 = 2/φ = 2(φ-1)? 因为 1/φ = φ-1. 其实 φ = (1+√5)/2, 1/φ = (√5-1)/2. r1 = √5 - 1. 因为 2/φ = 2*(√5-1)/2 = √5 - 1.
r2 = -2φ = - (1+√5) = -√5 - 1.
让我们检查 r1 和 r2 是否是 4-2z-z^2=0 的根:
r1 = √5 - 1 ≈ 2.236-1=1.236. 4 - 2(1.236) - 1.236^2 = 4 - 2.472 - 1.527 ≈ 0. 正确。
r2 = -√5 -1 ≈ -3.236. 4 - 2(-3.236) - (10.47) = 4+6.472-10.47 ≈ 0. 正确。
我们也有 φ = (1+√5)/2.
D 和 E 也用 √5 表示。
之前我们有:
D = -2/(φ^2 √5)
E = 2φ^2 / √5.
φ^2 = φ+1 = (3+√5)/2.
1/φ^2 = 2/(3+√5) = 2(3-√5)/(9-5) = (6-2√5)/4 = (3-√5)/2.
所以:
D = -2 * (3-√5)/2 / √5 = -(3-√5)/√5 = 1 - 3/√5? 不,-(3-√5)/√5 = -3/√5 + 1.
E = 2 * ((3+√5)/2) / √5 = (3+√5)/√5 = 3/√5 + 1.

验证: D + E? 不需要。再检查 D 和 E 的值:
用 r1, r2 表示 D 和 E:
我们有部分分式: (2z-4)/((z-r1)(z-r2)) = D/(z-r1) + E/(z-r2)
D = (2r1-4)/(r1-r2). r1 = √5-1, r2 = -√5-1. r1-r2 = 2√5.
2r1-4 = 2√5 -2 -4 = 2√5 -6 = 2(√5-3).
所以 D = 2(√5-3) / (2√5) = (√5-3)/√5 = 1 - 3/√5. 这与 -3/√5 + 1 相同。正确。
E = (2r2-4)/(r2-r1) = (2(-√5-1)-4)/(-2√5) = (-2√5 -2 -4)/(-2√5) = (-2√5-6)/(-2√5) = (√5+3)/√5 = 1 + 3/√5. 正确。

因此 P(n) = -1 + D * r1^{n-1}/(1 - r1^n) + E * r2^{n-1}/(1 - r2^n).

可以简化:
注意到 r1 = √5-1, r2 = -(√5+1). 且 r1 * r2 = -(5-1) = -4? 检查:(√5-1)(-√5-1) = - (√5-1)(√5+1) = - (5-1) = -4.
而 G(z) 的分子是 z^2, 分母是 4-2z-z^2,根为 r1, r2.
可能可以写成更对称的形式。
P(n) = -1 + D * r1^{n} / (r1 - r1^{n+1})? 原来的形式是 r1^{n-1}/(1 - r1^n) = r1^{-1} * r1^n/(1-r1^n) = (1/r1) * (1/(r1^{-n} - 1)). 但先保留。

我们要求 P(n) = a/b 模 p 的值。由于 p = 1 000 000 009。我们需要在模 p 下计算。这里的表达式中含有 √5。这提示我们需要在模 p 下求 √5 是否存在,或者扩展域。如果 5 是模 p 的二次剩余,则可以在模 p 整数中计算;否则需要扩展到 F_{p^2}。不管哪种,因为 p 是固定的,我们可以在代码中处理模运算。因为最终结果是模 p 下的有理数(a * b^{-1} mod p),所以只要在合适的域中计算即可,结果一定是 F_p 中的元素。

我们还需要注意 P(n) 的最简分数 a/b,但 Q 定义为 a ≡ b q (mod p) 的最小正整数 q,即 q ≡ a * b^{-1} (mod p)(取模 p 的最小正剩余)。我们只需直接计算 P(n) mod p,即计算有理数 a/b mod p 的值(即 a * inv(b) mod p)。因为如果我们在模 p 下计算 P(n) 为一个整数(通过模逆元),那正是 q。

所以任务归结为:计算 P(10^18) mod p,其中 p = 1000000009。

我们有闭式:
P(n) = -1 + D * r1^{n-1}/(1 - r1^n) + E * r2^{n-1}/(1 - r2^n)
其中 D = 1 - 3/√5, E = 1 + 3/√5, r1 = √5 - 1, r2 = -√5 - 1.

注意 r1 和 r2 都是代数整数。也可以将其完全有理化。
让我们用斐波那契和卢卡斯数表示。
已知斐波那契数 F_k = (φ^k - (-φ)^{-k})/√5.
卢卡斯数 L_k = φ^k + (-φ)^{-k}.
我们有 r1 = √5 - 1 = 2/φ? 因为 φ = (1+√5)/2, 2/φ = √5 -1. 正确。
r2 = -√5 - 1 = -2φ? 2φ = 1+√5, -2φ = -1-√5. 正确。
那么 r1^n = (√5 - 1)^n, r2^n = (-√5 - 1)^n.

我们也许可以找到 P(n) 的简单闭式。另一种方法:已知 P(n) = sum_{k≡0} F_{k-1}/2^k. 可直接用斐波那契数列模某种周期性?但我们只需计算一个具体的 n = 10^18,利用闭式在模 p 下计算。我们可以使用快速幂来计算 r1^n 和 r2^n。这需要处理 √5。在模 p 下,√5 可能存在也可能不存在。但我们总是可以定义扩展域 F_p[√5] 进行计算,最终结果一定会落在 F_p 中(因为 P(n) 是有理数)。这样我们就可以直接计算 a/b mod p。

但注意:p=1000000009。我们需要确定 5 是否是模 p 的二次剩余。计算 Legendre 符号 (5/p)。p mod 5 = 1000000009 mod 5 = 4,因为 1000000000 是 5 的倍数,1000000009 ≡ 4 mod 5。 (5/p) = (p/5) 根据二次互反律,因为 5 ≡ 1 mod 4。 (p/5) = (4/5) = 1(因为 4 是平方剩余,2^2=4)。所以 5 是模 p 的平方剩余!因此 √5 在 F_p 中存在。我们可以直接计算模 p 下的平方根。

求 √5 mod 1000000009。可以使用 Tonelli-Shanks 或者 Cipolla 算法。在代码中实现求平方根。或者预先计算好,因为 p 是固定的。我们可以写一个辅助函数。

这样,所有运算都在 F_p 中进行,使用整数模 p。计算 D, E, r1, r2 模 p。然后计算 r1^n, r2^n 模 p。注意 n = 10^18,非常大。指数需要模 p-1 = 1000000008。因为对于底数属于 F_p,其阶整除 p-1。所以指数可以模 p-1 约简: n mod (p-1) 用于计算幂。但注意:分母中有 1 - r^n。如果 r^n = 1 mod p,则分母为零!这种情况会发生吗?p-1 = 2^3 * 3 * 7 * 11 * 13 * 17 * 19 * 23 * 29? 不一定。我们需要检查 r1 和 r2 的阶。但概率不会导致分母为零?如果 r^n = 1,则表达式中的分母为零,但极限下 P(n) 仍有限。不过实际 n=10^18 mod (p-1) 可能恰好导致分母为零吗?我们需要在计算时处理:如果 1 - r1^n ≡ 0 mod p,则不能直接用公式。可能我们可以用另一种公式,或者先计算有理表达式避免除法。我们可以检查 n mod (p-1) 后是否使分母为零。

让我们先找到 √5 mod p。我们可以写程序,但这里我们只在文本中推理?用户要求“请给我cpp实现”。所以我们需要提供 C++ 代码。代码需要:

计算 sqrt(5) mod p = 1000000009.

然后进行模运算。

需要计算 P(10^18) 模 p,返回结果 q。

我们需要在代码中处理大指数模运算。可以使用 __int128 或直接用 64 位(因为 p 约为 1e9,乘法和幂需要用 64 位防止溢出,使用 unsigned long long 并做模乘)。

同时注意 Q 函数要求最小正整数 q,这正好是我们计算出的模逆值(0 < q < p)。

好的,让我们简化公式,也许可以进一步用斐波那契和卢卡斯数表示,这样可以直接用整数递推,无需显式求 √5。但因为我们只需一次计算,用 √5 方法直接。

简化 P(n) 公式:
P(n) = -1 + D * r1^{n-1}/(1 - r1^n) + E * r2^{n-1}/(1 - r2^n).
代入 D = 1 - 3/√5, E = 1 + 3/√5.
可以合并:
P(n) = -1 + (1 - 3/√5)(r1^{n-1}/(1-r1^n)) + (1 + 3/√5)(r2^{n-1}/(1-r2^n)).
我们也可以写:
P(n) = -1 + r1^{n-1}/(1-r1^n) + r2^{n-1}/(1-r2^n) + (3/√5)*( - r1^{n-1}/(1-r1^n) + r2^{n-1}/(1-r2^n) ).

但我们有 r2 = -4/r1? 因为 r1*r2 = -4. 所以 r2 = -4/r1. 这可能可以进一步简化。另外,也许我们可以将其写为关于 r1^n 和 r2^n 的对称函数。最终分母会是 (1-r1^n)(1-r2^n) = 1 - (r1^n+r2^n) + (r1 r2)^n = 1 - L_n' + (-4)^n? 注意 r1 + r2 = -2? r1+r2 = (√5-1)+(-√5-1) = -2. r1 r2 = -4. 所以 r1, r2 是方程 x^2 + 2x - 4 = 0 的根。设 V_n = r1^n + r2^n. 这是一个卢卡斯序列。V_0 = 2, V_1 = -2, V_{n+1} = -2 V_n + 4 V_{n-1}? 根据递推 x^2 = -2x + 4 => V_{n} = -2V_{n-1} + 4V_{n-2}. 我们可以利用模 p 下的递推计算 V_n 和 r1^n - r2^n。由于 √5 在模 p 下存在,我们可以直接用 r1,r2 进行幂运算。这并不难。

但是否可能 1 - r1^n ≡ 0 mod p?若 r1^n ≡ 1 mod p,则 r1 的阶整除 n。n=10^18 mod (p-1)。我们需要评估阶。p-1 = 1000000008 = 2^3 * 3 * 7 * 11 * 13 * 17 * 19 * 23 * 29? 让我们分解:1000000008 / 8 = 125000001. 125000001 / 3 = 41666667. 41666667 / 7? 7*5952381=41666667? 检查:7*5952381=41666667. 继续。我们不需要分解它,但我们可以在程序中计算 n_mod = 10^18 % (p-1),然后计算 r1^(n_mod) 等。若结果为1,则分母为零,我们需要特殊处理。但考虑到随机性和问题设计,可能分母不为零。为了安全,我们可以用符号计算方法:将整个 P(n) 表示为分式,分子和分母,然后代入数。分子分母都是整数(在 F_p 中),我们最终做一次逆元。

我们可以将 P(n) 通分为:
P(n) = [ - (1-r1^n)(1-r2^n) + D r1^{n-1}(1-r2^n) + E r2^{n-1}(1-r1^n) ] / [ (1-r1^n)(1-r2^n) ].
这可以完全展开并简化。这可以避免分母为零的中间步骤吗?如果分母为零,分子也为零,我们可以取极限。但实际上我们期望分母模 p 非零。

由于 p 很大且 n=10^18 是固定的,在代码中我们会计算 n_mod_p1 = n % (p-1)。如果 r1^{n} ≡ 1 则处理。不过,我们还可以从另一个角度:P(n) 是有理数,我们可以推导有理封闭形式。

已知对于一般的n,P(n) 的表达式是什么?
让我们尝试推导:
从 P(n) = -1 + D r1^{n-1}/(1-r1^n) + E r2^{n-1}/(1-r2^n).
注意到 D = 1 - 3/√5, E = 1 + 3/√5.
设 u = r1^n, v = r2^n. 注意 r1 r2 = -4.
1 - u 和 1 - v.
我们要计算 P(n).
我们可以用 u+v 和 u-v 等表达。因为 r1, r2 含有 √5,我们可以用斐波那契/卢卡斯数表达。
已知 r1 = √5 - 1, r2 = -√5 - 1.
考虑斐波那契数 F_k 和卢卡斯数 L_k,但它们通常是关于 φ 和 -1/φ 的。这里我们的底数是 r1, r2。我们可以直接计算 u, v。

由于我们只需要写代码,我们可以在模 p 扩展中计算,或者直接在 F_p 中计算(因为 √5 存在)。

最简单的实现方案:

使用 C++,定义模数 p = 1000000009。

编写 modpow, modinv。

求 √5 mod p。可以使用标准算法。因为 p ≡ 1 mod 4? p = 1000000009 ≡ 1 mod 4? 1000000009 % 4 = 1。可以使用 Tonelli-Shanks,或者因为 5 是二次剩余,我们可以用 sqrt 函数?一般用模平方根算法。

然后设 sqrt5 = 计算出的值。

计算 r1 = (sqrt5 - 1 + p) % p; r2 = (-sqrt5 - 1 + p) % p; (注意模 p)

计算 D = (1 - 3 * modinv(sqrt5) % p + p) % p; E = (1 + 3 * modinv(sqrt5) % p) % p;

计算 n_mod = 10^18 % (p-1) (因为费马小定理,底数与 p 互素时指数可模 p-1。但底数 r1 或 r2 是否与 p 互素?需要检查。r1 = √5-1。若 √5-1 ≡ 0 mod p,则 √5 ≡ 1 => 5 ≡ 1 mod p,不对。所以互素。所以可约简指数。)

计算 u = modpow(r1, n_mod), v = modpow(r2, n_mod).

分母1: den1 = (1 - u + p) % p; den2 = (1 - v + p) % p.

计算 term1 = D * modpow(r1, (n_mod - 1 + p-1) % (p-1)) % p * modinv(den1) % p; 等等。注意如果 n_mod == 0,则 n_mod - 1 应为 p-2。

最后 P_mod = (-1 + term1 + term2) % p,再转为正数。结果就是所求的 q。

但需要验证 P(2) 和 P(3) 来确保公式正确。
让我们验证:对于 n=2。
n=2, sqrt5 在模任何素数?我们先不模,直接代数计算。
r1 = √5-1, r2 = -√5-1.
u = r1^2 = (√5-1)^2 = 5 -2√5 +1 = 6 - 2√5.
v = r2^2 = (√5+1)^2 = 6 + 2√5.
1 - u = 1 - 6 + 2√5 = -5 + 2√5.
1 - v = 1 - 6 - 2√5 = -5 - 2√5.
r1^{1} = √5-1, r2^{1} = -√5-1.
D = 1 - 3/√5 = (√5-3)/√5.
E = 1 + 3/√5 = (√5+3)/√5.
term1 = D * r1 / (1-u) = (√5-3)/√5 * (√5-1) / (2√5-5).
term2 = E * r2 / (1-v) = (√5+3)/√5 * (-√5-1) / (-5-2√5).
计算:
1-u = 2√5-5 = √5(2 - √5)? 注意 2√5-5 = √5(2 - √5) = √5(2-√5). 而 r1 = √5-1 = -(1-√5).
term1 = (√5-3)/√5 * (√5-1) / (2√5-5).
注意 (√5-3)(√5-1) = 5 -√5 -3√5 +3 = 8 -4√5 = 4(2 - √5).
分母:√5 * (2√5-5) = √5 * √5(2-√5)? 2√5-5 = √5(2-√5)? 不对,√5 * √5 = 5. √5(2-√5) = 2√5 - 5,正确。所以分母 = √5 * √5(2-√5) = 5(2-√5).
因此 term1 = 4(2-√5) / (5(2-√5)) = 4/5.
term2: (√5+3)/√5 * (-√5-1) / (-5-2√5) = (√5+3)(√5+1) / (√5(5+2√5))? 注意负号:分子有负,分母有负,抵消。 (-√5-1) = -(√5+1). (-5-2√5) = -(5+2√5). 所以正。
(√5+3)(√5+1) = 5 + √5 + 3√5 + 3 = 8 + 4√5 = 4(2+√5).
分母:√5(5+2√5) = 5√5 + 10 = 5(√5+2) = 5(2+√5).
所以 term2 = 4(2+√5) / (5(2+√5)) = 4/5.
P(2) = -1 + 4/5 + 4/5 = 3/5。正确!

对于 n=3:
我们可以在代码中验证。目前公式正确。

因此我们的推导是正确的。

现在考虑实现细节:

我们需要计算 sqrt(5) mod 1000000009。
p = 1000000009,是质数吗?是的,1000000009 是已知质数。
计算 sqrt(5) mod p。可以使用 Tonelli-Shanks 算法。

我们来写一个模平方根函数。或者,因为代码是固定的,我们可以预先计算(通过外部工具),但作为完整 C++ 实现,最好包含计算过程。我们可以简单地用 sqrt 函数通过检查。由于 p 约 1e9,我们可以用暴力?不行。需要使用算法。

Tonelli-Shanks 算法步骤:
输入:素数 p,整数 n(此处 n=5)。
前提:n 是二次剩余 mod p。

找到 Q, S 使得 p-1 = Q * 2^S,Q 奇数。

找一个二次非剩余 z。

初始化:
M = S
c = z^Q mod p
t = n^Q mod p
R = n^{(Q+1)/2} mod p

循环 while t != 1:
找最小 i (1 <= i < M) 使得 t^{2^i} ≡ 1 mod p。
令 b = c^{2^{M-i-1}} mod p
更新: M = i, c = b^2 mod p, t = t * b^2 mod p, R = R * b mod p

返回 R(或 p - R,取较小正数?R^2 ≡ n mod p,取哪个都行)。

我们需要一个函数判断二次剩余并找到平方根。

也可以使用 Cipolla 算法,可能实现更简单(在扩展域中计算 (a + sqrt(a^2 - n))^{(p+1)/2})。我们可以在 F_p[√(a^2 - n)] 中运算。不过 Tonelli-Shanks 也不错。

由于用户明确要 C++ 实现,我将提供一个完整的 C++ 程序。

程序结构:

定义模数 P = 1000000009LL。

编写取模加法、乘法、幂、逆元。

实现 Tonelli-Shanks 求模平方根。

计算 sqrt5。

根据公式计算 P_n mod P。

打印结果。

边缘情况:如果 1 - u ≡ 0 mod P 或 1 - v ≡ 0 mod P。我们要检查。若发生,则需要极限计算。由于概率上极不可能,且问题设定下应不会发生,但我们可以加一个断言或处理。

我们需要计算 n = 10^18。在 C++ 中可以用 unsigned long long 存储(最大 ~1.8e19)。所以 n 可以放进去。

指数模 p-1:n_mod = n % (P-1)。注意 n 是 10^18,P-1 = 1000000008,都小于 2^63。我们直接做 n % (P-1)。

计算 r1^{n_mod - 1} mod P。如果 n_mod == 0,则指数为 -1 mod (P-1) 即 P-2。因为 r1^{p-1} = 1(费马小定理)。

我们可以先算 u = pow(r1, n_mod), v = pow(r2, n_mod)。
然后分母 den1 = (1 - u + P) % P; den2 = (1 - v + P) % P。
我们需要 den1 和 den2 的逆元。如果某个为零,则需特殊处理。我们暂且假设非零。

最后 ans = (P - 1 + term1 + term2) % P; 由于 ans 应为正数,确保在 1 到 P-1 之间。

让我们再检查下公式是否需要调整:
我们之前有 P(n) = -1 + D * r1^{n-1}/(1 - r1^n) + E * r2^{n-1}/(1 - r2^n).
注意 n 可能大于 P-1,但我们用 n_mod = n % (P-1) 计算 r1^n 和 r2^n。指数模 P-1 是有效的吗?由于底数 r1, r2 ∈ F_p 且非零,根据费马小定理,r^{P-1} ≡ 1,所以 r^n = r^{n_mod}。正确。

但注意分子中有 r1^{n-1}。我们可以计算 pow(r1, (n_mod - 1 + P-1) % (P-1))。

验证:假设 n_mod = 0,那么 n ≡ 0 mod (P-1),即 n 是 P-1 的倍数。此时 r1^n ≡ 1,分母 den1 = 0。这就是我们提到的特殊情况。如果 n_mod = 0,则 u = 1, v = 1,分母为零。但 P-1 约 1e9,n = 1e18,n 是 P-1 的倍数吗?1000000008 * 1000 = 1,000,000,008,000 > 1e12, * 1e6 = 1e18? 1000000008 * 1000000000 = 1,000,000,008,000,000,000 ≈ 1e18,但我们 n = 1e18 正好是 10^18。10^18 是否等于 1,000,000,008 * k? 我们检查 10^18 / 1000000008 ≈ 999.999... 实际上 1000000008 * 1,000,000,000 = 1,000,000,008,000,000,000 不等于 10^18。10^18 = 1,000,000,000,000,000,000。所以 n = 10^18 不完全能被 1000000008 整除。n_mod 会是非零。但我们要确切计算 n_mod = 10^18 % 1000000008。在代码中直接计算。

所以分母应该非零。

还有一点:我们使用的公式中的 r1, r2, D, E 都是基于 √5 的。但 √5 的符号选择会影响 r1 和 r2 的角色?如果我们选择另一个平方根(即负根),则 r1 和 r2 会交换。公式中的 D 和 E 也会相应变化,但最终 P(n) 应该相同,因为它是基于 r1, r2 的对称表达式。实际上,如果我们取 sqrt5 = -sqrt5_original,那么 r1' = -√5 - 1 = r2, r2' = √5 - 1 = r1。D' = 1 - 3/(-√5) = 1 + 3/√5 = E, E' = 1 - 3/√5 = D。所以整个和式不变。因此平方根的选择不影响结果。

现在编写代码。

让我们先草拟代码流程:

cpp
#include <iostream>
#include <cstdint>
#include <cassert>

using namespace std;

typedef long long ll;
const ll P = 1000000009LL;

ll mod_add(ll a, ll b) { return (a + b) % P; }
ll mod_sub(ll a, ll b) { return (a - b + P) % P; }
ll mod_mul(ll a, ll b) { return (a * b) % P; }

ll mod_pow(ll a, ll e) {
ll res = 1;
a %= P;
while (e) {
if (e & 1) res = mod_mul(res, a);
a = mod_mul(a, a);
e >>= 1;
}
return res;
}

ll mod_inv(ll a) {
return mod_pow(a, P - 2);
}

// 求模平方根,假设 a 是二次剩余,p 是奇素数
ll mod_sqrt(ll a) {
if (a == 0) return 0;
if (mod_pow(a, (P - 1) / 2) != 1) {
// 不应该发生,因为 5 是二次剩余
return -1;
}
// 简单实现 Tonelli-Shanks
// 分解 p-1 = Q * 2^S
ll Q = P - 1;
ll S = 0;
while (Q % 2 == 0) {
Q /= 2;
S++;
}
// 找一个二次非剩余
ll z = 2;
while (mod_pow(z, (P - 1) / 2) != P - 1) {
z++;
}
ll M = S;
ll c = mod_pow(z, Q);
ll t = mod_pow(a, Q);
ll R = mod_pow(a, (Q + 1) / 2);
while (t != 1) {
// 找最小的 i 使得 t^{2^i} == 1
ll i = 1;
ll t2i = mod_mul(t, t);
while (t2i != 1) {
t2i = mod_mul(t2i, t2i);
i++;
}
ll b = mod_pow(c, 1LL << (M - i - 1));
M = i;
c = mod_mul(b, b);
t = mod_mul(t, c);
R = mod_mul(R, b);
}
return R;
}

int main() {
ll sqrt5 = mod_sqrt(5);
// 验证
assert(mod_mul(sqrt5, sqrt5) == 5);

ll r1 = mod_sub(sqrt5, 1); // sqrt5 - 1
ll r2 = mod_sub(P - sqrt5, 1); // -sqrt5 - 1
ll inv_sqrt5 = mod_inv(sqrt5);
ll D = mod_sub(1, mod_mul(3, inv_sqrt5)); // 1 - 3/sqrt5
ll E = mod_add(1, mod_mul(3, inv_sqrt5)); // 1 + 3/sqrt5

ll n = 1000000000000000000LL; // 10^18
ll n_mod = n % (P - 1);

ll u = mod_pow(r1, n_mod);
ll v = mod_pow(r2, n_mod);

ll den1 = mod_sub(1, u);
ll den2 = mod_sub(1, v);

// 如果分母为零,需要特殊处理,但这里假设不会
ll term1 = mod_mul(D, mod_mul(mod_pow(r1, (n_mod - 1 + P - 1) % (P - 1)), mod_inv(den1)));
ll term2 = mod_mul(E, mod_mul(mod_pow(r2, (n_mod - 1 + P - 1) % (P - 1)), mod_inv(den2)));

ll P_mod = mod_add(mod_sub(term1, 1), term2); // term1 + term2 - 1
P_mod = (P_mod + P) % P;
if (P_mod == 0) P_mod = P; // 最简分数下,q 应该是在 1..P-1 之间

cout << P_mod << endl;
return 0;
}
等等,要再检查一下 term1 和 term2 的指数。
term1 = D * r1^{n-1} / (1 - r1^n).
我们使用 r1^{n-1} mod P. 因为 n_mod = n % (P-1),那么 n = k(P-1) + n_mod.
r1^{n-1} = r1^{k(P-1) + n_mod - 1} = (r1^{P-1})^k * r1^{n_mod - 1} ≡ r1^{n_mod - 1} mod P.
如果 n_mod == 0,则 n_mod - 1 = -1 ≡ P-2 mod (P-1)。公式 (n_mod - 1 + P - 1) % (P-1) 正确。
因为 P-1 = 1000000008,n_mod 在 0 到 P-2 之间。所以这个指数处理正确。

但是要注意:如果 n_mod == 0,则 u = r1^0 = 1,分母为零。我们已经假设 n_mod 不为零。实际 n = 10^18,我们来算一下 n_mod:
P-1 = 1000000008.
10^18 / 1000000008 ≈ 999999992. 等等,我们来计算准确值:1000000008 * 999999992 = ?
1000000008 * 1,000,000,000 = 1,000,000,008,000,000,000.
10^18 = 1,000,000,000,000,000,000.
所以 1000000008 * 999,999,992 = 1000000008 * (1,000,000,000 - 8) = 1,000,000,008,000,000,000 - 8,000,000,064 = 999,999,999,999,999,936? 等等,我算错了。
1,000,000,008 * 1,000,000,000 = 1,000,000,008,000,000,000 (这是 1.000000008e18)
10^18 = 1,000,000,000,000,000,000 (这是 1e18)
这两个数不一样!10^18 比 1,000,000,008,000,000,000 小!
哦!1,000,000,008,000,000,000 是 1e18 + 8e9? 10^18 = 1,000,000,000,000,000,000. 1,000,000,008,000,000,000 是 1.000000008e18,大于 1e18。所以 1000000008 * 1,000,000,000 > 10^18.
10^18 / 1000000008 ≈ 999,999,992.000... 因为 1000000008 * 999,999,992 = ? 我们需要精确计算 n_mod = 10^18 % 1000000008.
10^18 = 1,000,000,000,000,000,000.
设 d = 1000000008.
计算 10^18 mod d:
我们可以用 10^18 = (10^9)^2。但更简单:在代码中它自然会算,因为 C++ 64 位可以处理 10^18。我们可以在代码中直接写 n = 1000000000000000000ULL; n_mod = n % (P-1); 编译器/运行时计算。这是精确的。

测试:n_mod = 10^18 % 1000000008。因为 1000000008 * 999999992 = ?
999999992 * 1000000008 = 999999992 * (1000000000 + 8) = 999999992000000000 + 7,999,999,936 = 999,999,999,999,999,936.
10^18 - 这个数 = 1,000,000,000,000,000,000 - 999,999,999,999,999,936 = 64.
所以 n_mod = 64!非常好,10^18 ≡ 64 mod 1000000008.

因此 n_mod = 64,非零。分母不会为零。很好。

现在公式中涉及 r1^{n_mod} 和 r2^{n_mod},n_mod = 64,很小,但为了通用性我们仍使用快速幂。

让我们验证 n_mod 的计算:
1000000008 * 999999992 = 1000000008 * (1000000000 - 8) = 1000000008000000000 - 8000000064 = 999999999999999936.
10^18 - 999999999999999936 = 64. 正确。所以 n_mod = 64。我们可以直接硬编码,但建议保留动态计算。

现在检查 term1 和 term2 中 r1^{n-1} 的指数:因为 n_mod = 64,所以 (n_mod - 1) = 63。
r1^{63} 和 r2^{63} 没问题。

现在我们只需要验证对于 n=2 和 n=3,我们的程序框架能否得出 3/5 和 9/31 模 P 的正确结果。我们可以在代码中做测试(但最终提交时不必包含测试,只是自己验证)。作为回答,我们可以提供带有主函数的完整 C++ 代码。

但要注意,我们推导的 P(n) 公式是否对所有 n 成立?我们是从 P(n) = (1/n) sum_{j} G(ω^j) 推导的,它依赖于 n 个根之和,但这是针对正整数 n 的精确公式。我们使用部分分式展开,这对所有 n 成立(只要 n 是正整数)。对于 n=2 和 3 已验证。所以公式正确。

现在实现细节:

使用 unsigned long long 还是 long long?由于 P ~ 1e9,乘法最大 (1e9-1)^2 ≈ 1e18,刚好在 64 位有符号正数范围内(最大 9.22e18)。为了安全,我们可以使用 unsigned long long 或 __int128,但普通乘法 (a * b) % P 如果 a,b < P,则 a*b < 1e18 < 2^63-1(约 9.22e18)。所以直接使用 long long 没问题。

快速幂中,我们使用 mod_mul。

现在让我们确保 Tonelli-Shanks 实现正确。
p = 1000000009.
p-1 = 1000000008 = 8 * 125000001? 125000001 是奇数吗?125000001 不能被 2 整除,是的。
所以 S = 3, Q = 125000001? 1000000008 / 8 = 125000001. 是的。
我们需要一个二次非剩余 z。测试 z=2: 2^((P-1)/2) mod P = 2^500000004 mod P. 是否等于 P-1? 由于 2 经常是二次非剩余 mod p 如果 p ≡ 3,5 mod 8? p=1000000009. 1000000009 mod 8 = 1000000009 % 8 = 1? 因为 1000000000 % 8 = 0, 所以 1000000009 % 8 = 1. 因此 2 是二次剩余(根据二次互反律补充律,2 是二次剩余 mod p 当 p ≡ ±1 mod 8)。所以 2 是二次剩余!我们需要找一个非剩余。测试 z=3?或者 z=5?我们可以简单地循环直到找到。通常很小。
我们可以在代码中循环 z=2,3,5,... 直到 Legendre 符号为 -1。
对于 p=1000000009,z=3 可能是非剩余?我们不需要预先知道,代码会循环找到。

注意 Tonelli-Shanks 的 while 循环内部:
寻找最小的 i 使得 t^{2^i} ≡ 1 mod P。
我们可以从 i=1 开始,设 cur = tt % P,如果 cur == 1 则找到 i=1;否则 cur = curcur % P 等。
需要小心不要让 i 超过 M。M 初始为 S=3。S 很小!因为 P-1 = 8 * 奇数,所以 S=3。那么 M 最多为 3。这使得 Tonelli-Shanks 非常简单。实际上 S=3,意味着我们只需要几轮。

我们可以手工预计算 sqrt5,但为了完整性,包含算法。

另外我们可以使用 Cipolla 算法:对于奇素数 p,计算平方根的 Cipolla 算法:
选择一个随机数 a,使得 (a^2 - n) 是二次非剩余,然后在域 F_p[√(a^2 - n)] 中计算 (a + √(a^2 - n))^{(p+1)/2} 的“实部”。这个可能更容易实现。但两种都可以。

在这里,我选择实现 Tonelli-Shanks,因为 S 很小,它非常快。

再审视 Tonelli-Shanks 步骤:

text
Q = P-1, S = 0
while Q % 2 == 0: Q/=2, S++
// 此时 p-1 = Q * 2^S
z = 2
while Legendre(z) != -1: z++
// z 是非剩余
M = S
c = z^Q % P
t = n^Q % P
R = n^((Q+1)/2) % P
while t != 1:
i = 1
t2i = t * t % P
while t2i != 1:
t2i = t2i * t2i % P
i = i + 1
b = c^{2^{M-i-1}} % P
M = i
c = b * b % P
t = t * c % P
R = R * b % P
return R
对于 p=1000000009,S=3, Q=125000001.
如果 t=1 初始,则直接返回 R。
我们可以在代码中实现。

现在验证我们的最终结果是否唯一确定。Q 函数定义:对于素数 p 和最简分数 a/b,Q(a/b, p) = 满足 a ≡ b q (mod p) 的最小正整数 q。也就是 q ≡ a * b^{-1} mod p 的最小正数。我们计算的 P_mod = P(n) mod p 就是 a * b^{-1} mod p 的标准表示(在 0 到 p-1 之间)。但 q 要求最小正整数,所以如果 P_mod == 0,那意味着 a ≡ 0 mod p,但 P(n) 概率不会是 0,且 p 很大,所以 P_mod 应该在 1..p-1 之间。我们直接输出 P_mod 即可。

接下来,我们再验证一下 P(n) 的公式是否可能产生负数模 p。我们已经做了 mod 处理。

现在写出最终的 C++ 代码。

我将代码结构化:定义常量 P = 1000000009;定义 n = 10^18。所有运算用函数封装。

还要注意:我们要确保 mod_inv 在参数为 0 时不调用。分母 den1, den2 不应为 0。如果出现 0,则说明 1 - r1^n ≡ 0,即 r1^n = 1。因为 n_mod = 64,我们应确认 r1^64 不为 1。我们不提前计算,但可以加断言。

代码中需要 #include <iostream> 等。

我将提供一个单独的文件,包含 main 函数,计算并输出结果。

让我们再确认 Q 函数的定义与我们的模计算结果一致。Q(a/b, p) = min q > 0 s.t. a ≡ b q (mod p)。我们知道 a/b 是最简分数,且 p 是素数,b 与 p 互素(否则模方程可能无解或分母为 p 的倍数)。由于 P(n) 分母可能包含 p 的因子吗?p=1000000009 是素数,P(n) 的分母可能很大,但一般不会刚好是 p 的倍数,因为 p 很大且是任意定的。如果 b ≡ 0 mod p,那么 Q 未定义?题目暗示可以计算,所以 b 与 p 互素。所以 mod_inv(b) 存在。我们计算 a * inv(b) mod p 即可。

现在编写代码。

注意,Tonelli-Shanks 中计算 c^{2^{M-i-1}}:因为 M 和 i 都很小,我们可以直接用循环计算指数。
用 b = c; for (int j=0; j < M - i - 1; ++j) b = mod_mul(b, b);

另外,对于 Legendre 符号 (a/p) 我们用 mod_pow(a, (P-1)/2) 如果等于 1 则是二次剩余,等于 P-1 则是非剩余。

我们来完成 C++ 代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <iostream>
#include <cassert>

typedef long long ll;

const ll P = 1000000009LL;
const ll N = 1000000000000000000LL; // 10^18

// 模运算
ll mod_add(ll a, ll b) { return (a + b) % P; }
ll mod_sub(ll a, ll b) { return (a - b + P) % P; }
ll mod_mul(ll a, ll b) { return (a * b) % P; }

ll mod_pow(ll a, ll e) {
ll res = 1;
a %= P;
while (e) {
if (e & 1) res = mod_mul(res, a);
a = mod_mul(a, a);
e >>= 1;
}
return res;
}

ll mod_inv(ll a) {
return mod_pow(a, P - 2);
}

// Legendre 符号 (a / P)
ll legendre(ll a) {
ll res = mod_pow(a, (P - 1) / 2);
return res == P - 1 ? -1 : res; // 1 表示平方剩余,-1 表示非剩余
}

// Tonelli-Shanks 求模平方根,假设 a 是二次剩余,P 是奇素数
ll mod_sqrt(ll a) {
if (a == 0) return 0;
assert(legendre(a) == 1); // 确保 a 是二次剩余

// 分解 P-1 = Q * 2^S,Q 为奇数
ll Q = P - 1;
ll S = 0;
while (Q % 2 == 0) {
Q /= 2;
++S;
}

// 找一个二次非剩余 z
ll z = 2;
while (legendre(z) != -1) ++z;

ll M = S;
ll c = mod_pow(z, Q);
ll t = mod_pow(a, Q);
ll R = mod_pow(a, (Q + 1) / 2);

while (t != 1) {
// 找最小的 i 使得 t^{2^i} ≡ 1 mod P
ll i = 1;
ll t2i = mod_mul(t, t);
while (t2i != 1) {
t2i = mod_mul(t2i, t2i);
++i;
}
ll b = c;
for (ll j = 0; j < M - i - 1; ++j)
b = mod_mul(b, b);
M = i;
c = mod_mul(b, b);
t = mod_mul(t, c);
R = mod_mul(R, b);
}
return R;
}

int main() {
// 1. 计算 √5 mod P
ll sqrt5 = mod_sqrt(5);
assert(mod_mul(sqrt5, sqrt5) == 5);

// 2. 根据推导的公式计算参数
ll r1 = mod_sub(sqrt5, 1); // √5 - 1
ll r2 = mod_sub(P - sqrt5, 1); // -√5 - 1
ll inv_sqrt5 = mod_inv(sqrt5);
ll D = mod_sub(1, mod_mul(3, inv_sqrt5)); // 1 - 3/√5
ll E = mod_add(1, mod_mul(3, inv_sqrt5)); // 1 + 3/√5

// 3. 指数化简 (模 P-1)
ll n_mod = N % (P - 1); // N = 10^18

// 4. 计算 r1^n 和 r2^n 模 P
ll u = mod_pow(r1, n_mod);
ll v = mod_pow(r2, n_mod);

ll den1 = mod_sub(1, u);
ll den2 = mod_sub(1, v);
// 分母不应为零(经计算 n_mod = 64,确实不为零)
assert(den1 != 0 && den2 != 0);

// 5. 计算两项的值
ll exp = (n_mod - 1 + P - 1) % (P - 1);
ll term1 = mod_mul(D, mod_mul(mod_pow(r1, exp), mod_inv(den1)));
ll term2 = mod_mul(E, mod_mul(mod_pow(r2, exp), mod_inv(den2)));

// 6. 合成最终结果 P(10^18) mod P
ll ans = mod_add(mod_sub(term1, 1), term2);
if (ans == 0) ans = P; // 最简分数下 q 应介于 1 和 P-1 之间

std::cout << ans << std::endl;
return 0;
}

fun fact

我以为直接求和就能算出来结果,但最后还是需要套生成函数.