公式

E - Multiple-Free Sequences 解説 by sheyasutaka


考察

漸化式 \(s_n = As_{n-1} + Bs_{n-2}\)\(s_n = (As_{n-1} + Bs_{n-2}) \bmod{M}\) で置き換えて得られる数列を考えます.この数列と元の数列は,各要素を \(\mod{M}\) のもとで比較すれば等しいです.この数列の要素はすべて \(0\) 以上 \(M-1\) 以下であり,問題文中の条件はこの数列が \(0\) を含むことと言い換えられます.以下ではこの数列を \(s\) として扱います.

\(0 \leq x, y \leq M-1\) の範囲の整数組 \((x, y)\) を状態にもつオートマトンをおき,状態 \((x, y)\) からは状態 \((y, (Ay + Bx) \bmod{M})\) にのみ遷移するとします.これは,数列 \(s\) の任意の位置にある連続 \(2\) 要素 \((s_{n-2}, s_{n-1})\) に対する「\(1\) つ次の位置」の連続 \(2\) 要素 \((s_{n-1}, s_{n-0})\) に対応します.

\(M = 4,\ A = 1,\ B = 2\) のとき (入力例 1) のオートマトンを以下の図に示します.

このとき,どのような初期値 \(s_1, s_2\) をもつ数列 \(s\) においても,オートマトン上で状態 \((s_{1}, s_{2})\) から \(k-1\) 回遷移した先は状態 \((s_{k}, s_{k+1})\) です.ここから,\(s\)\(0\) を含むことは,状態 \((s_{1}, s_{2})\) から任意回の遷移で状態 \((0, t)\) (\(t\) は任意) にたどり着けることとして言い換えられます.したがって,状態 \((s_1, s_2) = (x, y)\) がこれを満たすかどうかを \(M^2\) 通りの \((x, y)\) すべてについて判定できれば,満たさないものの個数が答えとなります.

状態 \((0, t)\) (\(t\) は任意) からなる集合を \(W\) とおきます.

オートマトンの状態・遷移を有向グラフの頂点・辺として見たとき,各頂点 \(v\) について,そこから集合 \(W\) 内のいずれかの頂点 \(w \in W\) にたどり着くパスが存在するかを判定できればよいです.これは,辺の向きを逆にした有向グラフ上で,\(W\) 内のいずれかの頂点 \(w \in W\) からその頂点 \(v\) にたどり着くパスが存在するかどうかとして言い換えられ,BFS や DFS といった探索によってまとめて結果を得ることができます.

時間計算量は \(O(M^2)\) となり,十分高速です.

実装例 (C++)

#include <iostream>
using std::cin;
using std::cout;
#include <string>
using std::string;
#include <algorithm>
#include <vector>
using std::vector;
#include <queue>
using std::queue;

typedef long long int ll;

ll m, a, b;

vector<vector<ll> > edges;
vector<ll> isseen;
void solve () {
	// backward edges
	edges.resize(m*m);
	for (ll i = 0; i < m; i++) {
		for (ll j = 0; j < m; j++) {
			// (i, j) <- (j, i*b+j*a)
			ll k = (i * b + j * a) % m;
			edges[j * m + k].push_back(i * m + j);
		}
	}

	// bfs init
	isseen.assign(m*m, 0);
	queue<ll> que;
	for (ll i = 0; i < m; i++) {
		for (ll j = 0; j < m; j++) {
			if (i == 0 || j == 0) {
				ll v = i*m + j;
				isseen[v] = 1;
				que.push(v);
			}
		}
	}
	// bfs run
	while (!que.empty()) {
		ll v = que.front();
		que.pop();

		for (ll u : edges[v]) {
			if (!isseen[u])	{
				isseen[u] = 1;
				que.push(u);
			}
		}
	}

	// count answers
	ll ans = 0;
	for (ll i = 0; i < m; i++) {
		for (ll j = 0; j < m; j++) {
			if (isseen[i * m + j] == 0) ans++;
		}
	}
	
	cout << ans << "\n";

}

int main (void) {
	std::cin.tie(nullptr);
	std::ios_base::sync_with_stdio(false);

	cin >> m >> a >> b;
	
	solve();

	return 0;
}

投稿日時:
最終更新: