AtCoder

ABC293 E問題(Geometric Progression)を解く

AtCoder_ABC293_E

AtCoder が提供しているABC(AtCoder Beginner Contest)293 のE問題をC++とPythonで解いてみました。ABC293は、2023年3月11日21:00に実施されました。

AtCoder の紹介はこちらに、プログラミングの方針はこちらに記事があります。

E問題 Geometric Progression(Difficulty : 1453)

問題はリンク先をご覧ください。

ABC293 E問題 Geometric Progression

ダブリングを使って解くことができました。AtCoder Problems による Difficulty は 1453 でした。

考察

この記事は、公式解説で紹介されている twitter解説の「E問題はダブリングなのだ!」を参考にしました。

X が 2 のべき乗の場合

$X$ が $2^i$ の場合は、以下の手順で値 dp[i] を求めることができます。なお、適当なタイミングで M で割った余りを求めることにして、M で割る式は省略します。

  • i = 0: dp[0] = $A^0 = 1$
  • i = 1: dp[1] = $1 + A^1 \times 1$ = dp[0] + $A^1 \times$ dp[0]
  • i = 2: dp[2] = $1 + A^1 + A^2(1 + A^1)$ = dp[1] + $A^2 \times$ dp[1]
  • i = 3: dp[3] = $1 + A^1 + A^2 + A^3 + A^4(1 + A^1 + A^2 + A^3)$
           = dp[2] + $A^4 \times$ dp[2]

まとめると、$X = 2^i$ の場合、以下の漸化式で求めることができます。

dp[i] = dp[i – 1] + $A^{2^{i-1}}\times$ dp[i – 1]

X が一般的な数の場合

次に一般的な $X$ について、値を求めます。

例えば、$X = 11 = 2^3 + 3$ について、次のように求めることができます。

$$\sum^{10}_{i = 0} A^i = 1 + A + \cdots + A^{10}$$

$= 1 + A + A^2 + A^3(1 + A + \cdots + A^7) = 1 + A + A^2 + A^3 \times$ dp[3]

前半は、以下のように書き換えることができます。

$= 1 + A + A^2 = 1 + A(1 + A) = 1 + A \times$ dp[1]

つまり、$X$ が $2^i$ の場合に値を求めておけば、$X = 11$ の場合は、$X = 8 + 2 + 1$ と分解して値を求めることができます。このように 2 のべき乗の場合を先に計算しておいて、値を求める手法をダブリングと呼ぶようです。

解答案

C++ プログラム例(ABC293E)

剰余付きの$N$乗を求めるために、繰り返し二乗法と呼ばれる手法を使いました。具体的な実装は、関数 power を参照してください。

上で考察したように2のべき乗に対して、配列 d[i](i = 0, …, 60)を求めておきます(23から28行目)。$X$ に対して、一番大きなビットから処理をしていきます。処理しているビットより小さな数は、変数 temp_p として求めて、残りの処理を行います。

以下が、C++プログラムとなります。

#include <bits/stdc++.h>
using namespace std;

typedef unsigned long long int ull;

int power(int n, ull p, int mod) {
	if (p == 0) {
		return 1;
	}
	if (p % 2 == 0) {
		long long int t = power(n, p / 2, mod);
		return (int)(t * t % mod);
	}
	return (int)(((long long int)n * power(n, p - 1, mod))% mod);
}

int main()
{
	ull a, x, m;
	cin >> a >> x >> m;

	vector<ull> dp(61);
	ull p = a;
	dp[0] = 1;
	for (int i = 1; i <= 60; ++i) {
		dp[i] = (dp[i - 1] + dp[i - 1] * p)% m;
		p = (p * p)% m;
	}

	ull result = 0;
	for (int i = 60; i >= 0; --i) {
		if (x & (1ULL << i)) {
			ull temp_p = x & ((1ULL << i) - 1);
			result += (dp[i] * power(a, temp_p, m))% m;
			result %= m;
		}
	}

	cout << result << endl;

	return 0;
}

AC(Accepted=正しいプログラム)と判定されました。

Python プログラム例(ABC293E)

Python では、剰余付きのべき乗を求める関数 pow が提供されています。基本的な考え方は、C++ 版を移植しました。

やはり、Python ではすっきりと書ける印象です。

"""AtCoder Beginner Contest 293 E"""
a, x, m = map(int, input().split())
dp = [0] * 61
dp[0] = 1
p = a
for i in range(1, 61):
    dp[i] = (dp[i - 1] + dp[i - 1] * p) % m
    p = p * p % m

result = 0
for i in range(60, -1, -1):
    if x & (1 << i):
        temp_p = x & ((1 << i) - 1)
        result += dp[i] * pow(a, temp_p, m)
        result %= m

print(result)

こちらも「AC」と判定されました。

最後に

問題を読んだときに、M が素数であれば、$(A^x-1)/(A-1)$ を求めれば良いことが分かりました。しかし、M が素数ではない場合に計算する工夫が分かりませんでした。公式解説では、このような場合も適用できる計算を紹介していました。この方法では非常にすっきりと書けています。

ただし、この方法は自分で思いつくことは難しく、また数論を学び直す気力もわかないため、「タブリング」というヒントの言葉から、プログラムを書いてみました。

引き続き ABC の問題を紹介していきます。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA