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++ 代码。
|