AtCoder

ABC321 C問題(321-like Searcher)を解く

AtCoder_ABC321_C

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

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

C問題 321-like Searcher(Difficulty : 591)

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

ABC321 C問題 321-like Searcher

321-like Number の構造に気が付けば解くことができます。AtCoder Problems による Difficulty は 591 でした。

解答案

この問題で与えられる $K$ について、$1 \leqq K$ という制約が示されています。上限の制約がありません。一方、「321-like Number は $K$ 個以上存在する」という制約があります。

一番大きな 321-like Number は、10桁の 9876543210 であることが分かります。その次に大きな数は、9桁の 987654321 となります。また、9桁の数は、0 から 9 を抜いた 10 個しかないことが分かります。10桁の 321-like Number がある一方、それほどの個数が無いことが予想できます。

以下は公式解説を参考にしました。

321-like Number は、同じ数字を2個含むことはありません。

  • 0 を含むか、含むかないかのどちらか。
  • 1 を含むか、含むかないかのどちらか。
  • 9 を含むか、含むかないかのどちらか。

各数字の有無をbit全探索で考えます。また、0は対象となりません。このため、すべて2進数が0の組み合わせと、0だけある場合(0000000001)は、対象となりません。このため、321-like Number は、$2^{10} – 2 = 1022$ 個しかありません。

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

bit全探索を使います。前述の考察からループを2から開始します(12行目)。大きな数から取り出すため、次のループを9から0の順番で処理しています(14行目)。

bit全探索でビットがセットされている数字を大きな順に並べて、配列 result に格納します。最終的に result をソートして解答します。

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

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

typedef long long int ll;

int main()
{
	int k;
	cin >> k;

	vector<ll> result;
	for (int i = 2; i < (1 << 10); ++i) {
		ll no = 0;
		for (int j = 9; j >= 0; --j) {
			if ((i & (1 << j)) != 0) {
				no = no * 10 + j;
			}
		}
		result.push_back(no);
	}
	sort(result.begin(), result.end());

	cout << result[k - 1] << endl;

	return 0;
}

優先度付きキューを使った別解

優先度付きキューを使ったプログラムも紹介します。

  • 0 から 9 までの数字をキューに格納します(12ー14行目)。
  • キューから取り出した数字の最大の桁の数字(tn)のひとつ大きな桁の数字(i)を設定してキューに格納します。
    • 例1)0 を取り出したら、10、20、…、90 を格納する。
    • 例2)10 を取り出したら、210、310、…、910 を格納する。
    • 例3)910 を取り出したら、格納する数字はない。
    • 例4)876543210 を取り出したら、9876543210 を格納する。
  • ある桁(たとえば2桁)の数をすべて処理したら、次の桁(3桁)の数の処理をします。
  • 321-like Number を小さい順に no で管理します。no = -1 としているのは、キューから取り出す 0 を 0 番目に、1 を 1 番目にするためです。

すでに考察したように、321-like Number は、1022個しかないため、このプログラムも十分に速く動作しました(C++20 で 6ms でした)。

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

typedef long long int ll;

int main()
{
	int k;
	cin >> k;

	priority_queue<ll, vector<ll>, greater<ll>> que;
	for (ll i = 0; i <= 9; ++i) {
		que.push(i);
	}

	ll base = 10;
	ll no = -1;
	while (1) {
		ll t = que.top();
		while (t < base) {
			que.pop();
			++no;
			if (no == k) {
				cout << t << endl;
				return 0;
			}
			ll tn = t / (base / 10);
			for (ll i = tn + 1; i <= 9; ++i) {
				que.push(i * base + t);
			}
			t = que.top();
		}
		base *= 10;
	}

	return 0;
}

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

Python プログラム例(ABC321C)

Python は、bit全探索のプログラムを移植しました。以下となります。

"""AtCoder Beginner Contest 321 C"""
k = int(input())

result = []
for i in range(2, 2**10):
    no = 0
    for j in range(9, -1, -1):
        if (i & (1 << j)) != 0:
            no = no * 10 + j
    result.append(no)
result.sort()

print(result[k - 1])

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

最後に

この問題は、当初 DP を使って解こうとしていました。うまく書くことができず、優先度付きキューを使う方法を思いつきましたが、コンテスト中は時間が足りませんでした。

公式解説を参考にbit全探索のプログラムを書いた後に、キューを使うプログラムでもACを取ることができました。勉強になりました。

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

COMMENT

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

CAPTCHA