AtCoder

ABC324 E問題(Joint Two Strings)を解く

AtCoder_ABC324_E

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

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

E問題 Joint Two Strings(Difficulty : 1153)

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

ABC324 E問題 Joint Two Strings

左右から部分文字列となっている文字数を求めて二分探索です。AtCoder Problems による Difficulty は、1153 でした。

解答案

文字列 $S_i$ は、文字列 $T$ を左から考えて left[i] 文字を部分文字列として含むとします。同じように右から考えて right[i] 文字を部分文字列として含むとします。

言葉では分かりにくいため、入力例1で考えます。文字列 $T$ は、”bac” です。

i文字列左から考えた部分文字数 left[i]右から考えた部分文字数 right[i]
0abba2文字(bとa)0文字
1bcb1文字(bのみ)1文字(cのみ)
2aaca0文字2文字(aとc)

ここまで求めておけば、以下が分かります。

  • $S_0$ を左側に持つ場合、$S_1$ と $S_2$ を右側に持てば合わせて “bac” を含む。
  • $S_1$ を左側に持つ場合、$S_2$ を右側に持つ文字列のみ “bac” を含む。
  • $S_2$ を左側に持つ場合、右側にどの文字列を持ってきても “bac” を含まない。

上記から、3通りの場合に合わせた文字列は ”bac” を含むことが分かります。

同じように left[i], right[i] を求めて、left[i] + right[j] が $T$ の文字数以上になる場合の数を求めます。具体的には、ソートした right に対して、二分探索で($T$ の文字数 – left[i])以上となる個数から、場合の数を求めることができます。

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

上の解説の手順を実装しました。

  • 配列 left[i] を求めます。部分文字列として一致した文字の場所を、変数 ti に格納しておきます(16ー28行目)。
  • 配列 right[i] を求めます。部分文字列として一致した文字の場所を、変数 ti に格納しておきます(29ー41行目)。
  • 二分探索ができるように right をソートします(42行目)。
  • 配列 right に対して($T$ の文字数 – left[i])以上となる要素の個数を求めて、変数 result に加えていきます(44ー47行目)。
  • 変数 result を出力します(49行目)。

プログラムは少し長くなりました。ただし、コードのそれぞれの塊はやることが明確になっています。以下が、C++プログラムとなります。

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

typedef long long int ll;

int main()
{
	int n;
	string t;
	cin >> n >> t;
	vector<string> s(n);
	for (int i = 0; i < n; ++i) {
		cin >> s[i];
	}

	vector<int> left(n, 0);
	for (int i = 0; i < n; ++i) {
		int ti = 0;
		for (int j = 0; j < s[i].length(); ++j) {
			if (s[i][j] == t[ti]) {
				++ti;
				if (ti == t.length()) {
					break;
				}
			}
		}
		left[i] = ti;
	}
	vector<int> right(n, 0);
	for (int i = 0; i < n; ++i) {
		int ti = t.length() - 1;
		for (int j = s[i].length() - 1; j >= 0 ; --j) {
			if (s[i][j] == t[ti]) {
				--ti;
				if (ti == -1) {
					break;
				}
			}
		}
		right[i] = (t.length() - 1) - ti;
	}
	sort(right.begin(), right.end());

	ll result = 0;
	for (int i = 0; i < n; ++i) {
		result += n - (lower_bound(right.begin(), right.end(), t.length() - left[i]) - right.begin());
	}

	cout << result << endl;

	return 0;
}

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

Python プログラム例(ABC324E)

基本的な考え方は同じです。モジュール bisect を import して、bisect.bisect_left を呼び出して二分探索しています(2、30行目)。

"""AtCoder Beginner Contest 324 E"""
import bisect

n, t = input().split()
n = int(n)
s = [input() for i in range(n)]

left = [0] * n
for i in range(n):
    ti = 0
    for j in range(len(s[i])):
        if s[i][j] == t[ti]:
            ti += 1
            if ti == len(t):
                break
    left[i] = ti
right = [0] * n
for i in range(n):
    ti = len(t) - 1
    for j in range(len(s[i]) - 1, -1, -1):
        if s[i][j] == t[ti]:
            ti -= 1
            if ti == -1:
                break
    right[i] = len(t) - 1 - ti
right.sort()

result = 0
for i in range(n):
    result += n - bisect.bisect_left(right, len(t) - left[i])

print(result)

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

最後に

この問題は、コンテスト中に解くことができました。二分探索の考え方に慣れてきた気がします。

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

COMMENT

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

CAPTCHA