C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で関数 Y = (X^6 + X^2 + 9894845) % 971 の値を効率的に求める方法

問題概要

次のような関数が与えられます。

f(x) = (x6 + x2 + 9894845) % 971

任意の x の値に対して f(x) の値を求めるのが課題です。例えば x = 5 を入力した場合、出力は 469 になります。

アプローチ:繰り返し二乗法(バイナリ法)

x をそのまま6乗すると値が非常に大きくなり、64ビット整数でもオーバーフローする恐れがあります。そこで、べき乗の計算途中で都度剰余を取りながら値を小さく保つ「繰り返し二乗法」を利用します。この方法なら計算量は指数のビット数に比例する O(log e) で済み、大きな x に対しても高速かつ安全に計算できます。

アルゴリズムの手順

  • power_mod() 関数を定義します。この関数は底(base)、指数(exponent)、法(modulus)の3つの引数を受け取ります。
  • base := base mod modulus とする。
  • result := 1 で初期化する。
  • exponent > 0 の間、次の処理を繰り返します。
    • exponent が奇数の場合: result := (result × base) mod modulus
    • base := (base × base) mod modulus
    • exponent := exponent ÷ 2(1ビット右シフト)
  • result を返します。
  • main 側では次の式で最終結果を求めます。
    ((power_mod(n, 6, m) + power_mod(n, 2, m)) % m + 355) % m

なお、式中の定数 355 は 9894845 % 971 = 355 となる事前計算済みの剰余です。定数項をあらかじめ約分しておくことで、計算をシンプルにしています。

実装例

理解を深めるために、以下のC++による実装を見てみましょう。

#include <bits/stdc++.h>
using namespace std;
typedef long long int lli;

lli power_mod(lli base, lli exponent, lli modulus) {
    base %= modulus;
    lli result = 1;
    while (exponent > 0) {
        if (exponent & 1)
            result = (result * base) % modulus;
        base = (base * base) % modulus;
        exponent >>= 1;
    }
    return result;
}

int main() {
    lli n = 654654, m = 971;
    cout << (((power_mod(n, 6, m) + power_mod(n, 2, m)) % m + 355) % m);
}

入力

n = 654654

出力

450

まとめ

このように、繰り返し二乗法を使えば大きな指数を持つべき乗の剰余も高速かつ安全に計算できます。競技プログラミングや暗号処理などでも広く使われる基本テクニックなので、実装パターンごと覚えておくと便利です。

  1. C++で解く!Nの階乗の16進数表現における末尾のゼロの個数の求め方

    この記事では、与えられた整数Nの階乗(N!)を16進数で表したとき、末尾に何個のゼロが連続するかを求める問題について詳しく解説します。 入力 : N = 7 出力 : 1 説明 : fact(7) = 5040(10進数)で、16進数では13B0となり、末尾のゼロは1個です。 入力 : N = 11 出力 : 2 説明 : fact(11) = 39916800(10進数)で、16進数では2611500となり、末尾のゼロは2個です。 10進数から16進数への変換のおさらい まず、任意の10進数を別の基数へ変換する手順をおさらいしましょう。ここでは、(5040)10 を16進数に変換する例を

  2. C++の二分探索木(BST)で最小値のノードを見つける方法

    二分探索木(Binary Search Tree、BST)が与えられたとき、その木の中から最小の要素を見つけることを考えます。例えば、以下のようなBSTがあるとします。この場合、最小要素は 1 になります。考え方二分探索木の重要な性質として、左部分木には必ず親ノードより小さい値が格納されるというものがあります。この性質を利用すると、次の手順で最小要素を見つけることができます。ルートノードから探索を開始します。現在のノードの左の子が NULL でない間、左の子へ移動を繰り返します。左の子が NULL になったノードの値が、木全体の中で最小の要素です。この操作の計算量は木の高さに依存し、平衡な二分