AtCoder

ABC341 E問題(Alternating String)を解く

AtCoder_ABC341_E

AtCoder が提供しているABC(AtCoder Beginner Contest)341 のE問題をC++で解いてみました。ABC341は、2024年2月17日21:00に実施されました。

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

E問題 Alternating String(Difficulty : 1223)

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

ABC341 E問題 Alternating String

一工夫して、セグメント木を使います。AtCoder Problems による Difficulty は 1223 でした。

解答案

文字列 $S$ の $i$ 文字目と $i+1$ 文字目が同じであれば値 $0$ を持ち、異なれば値 $1$ を持つ配列を計算します。入力例1をもとに計算してみます。

入力例1 文字列 $S$ の初期値は以下です。

1 0 1 0 0

差分の配列は、以下となります。

1 1 1 0

最初に、クエリ2(1、3)を調べます。

1 0 1 0 0

差分配列の該当する箇所は、以下となります。出力は、Yes です。

1 1 1 0

次のクエリ2(1、5)を調べます。

1 0 1 0 0

差分配列の該当する箇所は、以下となります。出力は、No です。

1 1 1 0

次にクエリ1(1、4)で文字列を書き換えます。

0 1 0 1 0

差分配列を更新します。変化した文字を赤字にします。

1 1 1 1

再びクエリ2(1、5)を調べます。

0 1 0 1 0

差分配列の該当する箇所は、以下となります。出力は、Yes です。

1 1 1 1

次にクエリ1(3、3)で文字列を書き換えます。

0 1 1 1 0

差分配列を更新します。変化した文字を赤字にします。

1 0 0 1

次のクエリ2(2、4)を調べます。

0 1 1 1 0

差分配列の該当する箇所は、以下となります。出力は、No です。

1 0 0 1

差分配列の値を見れば、以下が分かります。

  • クエリ1は、文字列を置き換えても、差分の配列は、端の2か所しか変化しない。
    • 0と1が反転しても、差分配列の途中の値は変わらない。
  • クエリ2は、対応する差分の配列がすべて1であれば Yes を、それ以外の場合は No を出力する。

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

セグメント木を使えば、端の値を更新しながら、指定された範囲の合計を求めることができます。この記事で紹介しています。差分配列をセグメント木に載せます。

注意
この問題では、クエリ1とクエリ2の添え字は、1からカウントしています。セグメント木の要素は、[0, N] の領域を確保しておき、差分配列を [1, N-1] = [1, N) に格納すれば、1からカウントする場合と整合が取れます。上に示した例と照らし合わせて確認してください。

  • 文字列 $S$ の $i$ 文字目と $i+1$ 文字目の差分配列をセグメント木 $i+1$ 番目の要素に格納します(80ー81行目)。
  • クエリ1(L、R)に対して、セグメント木の L-1 番目の要素と R 番目の要素を更新します。注意としては、LとRは問題に与えられた1からカウントした値を使います(89ー90行目)。
  • クエリ2(L、R)に対して、セグメント木の [L, R) の範囲の値がすべて1になっているか(つまり R-L になっているか)調べます(92行目)。

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

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

class SegmentTree {
private:
	vector<int> data;
	int (*op)(int, int);
	int e;

public:
	int size;

	SegmentTree(int n, 	int (*_op)(int, int), int _e) {
		op = _op;
		e = _e;
		size = 1;
		while (size < n) {
			size *= 2;
		}
		data.assign(2 * size - 1, e);
	}

	int get(int k) {
		return data[k + size - 1];
	}

	void update(int k, int a) {
		k += size - 1;
		data[k] = a;
		while (k > 0) {
			k = (k - 1) / 2;
			data[k] = op(data[k * 2 + 1], data[k * 2 + 2]);
		}
	}

	int query(int a, int b) {
		return query_sub(a, b, 0, 0, size);
	}

	// call ST.query(a, b, 0, 0, size);
	int query_sub(int a, int b, int k, int l, int r) {
		if ((r <= a)||(b <= l)) {
			return e;
		}
		if ((a <= l)&&(r <= b)) {
			return data[k];
		} else {
			int vl = query_sub(a, b, k * 2 + 1, l, (l + r) / 2);
			int vr = query_sub(a, b, k * 2 + 2, (l + r) / 2, r);
			return op(vl, vr);
		}
	}
};

int op(int a, int b)
{
	return a + b;
}

int main()
{
	int n, q;
	cin >> n >> q;
	string s;
	cin >> s;

	if (n == 1) {
		for (int i = 0; i < q; ++i) {
			int type, L, R;
			cin >> type >> L >> R;
			if (type == 2) {
				cout << "Yes" << endl;
			}
		}
		return 0;
	}

	SegmentTree st(n + 1, op, 0);
	for (int i = 0; i < n - 1; ++i) {
		if (s[i] != s[i + 1]) {
			st.update(i + 1, 1);
		}
	}

	for (int i = 0; i < q; ++i) {
		int type, L, R;
		cin >> type >> L >> R;
		if (type == 1) {
			st.update(L - 1, 1 - st.get(L - 1));
			st.update(R, 1 - st.get(R));
		} else if (type == 2) {
			if (st.query(L, R) == R - L) {
				cout << "Yes" << endl;
			} else {
				cout << "No" << endl;
			}
		}
	}

	return 0;
}

AtCoder では、AtCoder Library(ACL)と呼ばれるライブラリが提供されています。ACL で、セグメント木クラス segtree も提供されています。以下は、segtree を使ったプログラムです。

#include <bits/stdc++.h>
#include <atcoder/segtree>

using namespace std;
using namespace atcoder;

int op(int a, int b)
{
	return a + b;
}

int e(void)
{
	return 0;
}

int main()
{
	int n, q;
	cin >> n >> q;
	string s;
	cin >> s;

	if (n == 1) {
		for (int i = 0; i < q; ++i) {
			int type, L, R;
			cin >> type >> L >> R;
			if (type == 2) {
				cout << "Yes" << endl;
			}
		}
		return 0;
	}

	segtree<int, op, e> st(n + 1);
	for (int i = 0; i < n - 1; ++i) {
		if (s[i] != s[i + 1]) {
			st.set(i + 1, 1);
		}
	}

	for (int i = 0; i < q; ++i) {
		int type, L, R;
		cin >> type >> L >> R;
		if (type == 1) {
			st.set(L - 1, 1 - st.get(L - 1));
			st.set(R, 1 - st.get(R));
		} else if (type == 2) {
			if (st.prod(L, R) == R - L) {
				cout << "Yes" << endl;
			} else {
				cout << "No" << endl;
			}
		}
	}

	return 0;
}

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

Python のセグメント木について

Python は、セグメント木を標準ライブラリとしては用意していません。このブログの Python プログラムは、なるべく言語標準のライブラリで実装したいと考えています。このような事情で、この記事では、Python に移植したプログラムの紹介を省略します。

最後に

更新するクエリと結果を返すクエリを処理する問題でした。セグメント木を使うことは予想ができましたが、下準備を思いつきませんでした。

先週にセグメント木について記事を書いたため(記事1記事2記事3)、応用例を学んで使いこなせるようになりたいです。

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

COMMENT

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

CAPTCHA