AtCoder

ABC367 F問題(Rearrange Query)を解く

AtCoder_ABC367_F

AtCoder が提供しているABC(AtCoder Beginner Contest)367 F問題をC++とPythonで解いてみました。ABC367は、2024年8月17日21:00に実施されました。

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

F問題 Rearrange Query(Difficulty : 1540)

問題の詳細は、リンク先をご覧ください。

ABC367 F問題 Rearrange Query

変換した値の累積和を求めます。AtCoder Problems による Difficulty は 1540 でした。

解答案

制約 $1 \leqq N, Q \leqq 2 \times 10^5$ であるため、$O(NQ)$ となる処理は間に合いません(それぞれの数字の頻度の累積和を求めて、集合にある個数を求めるなど)。

部分集合が一致していれば、部分集合の和は一致しています。逆に部分集合の和が一致していても、部分集合が一致しているとは言えません。ここで [1, N] を [0, 264) にランダムにマッピングして和を求めれば、部分集合の和が一致しない多くの場合、部分集合も一致しなくなります。

1からNに対応する乱数を割り当てて、累積和を求めて、部分集合の和が一致するか確かめます。

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

[1, N] を [0, 264) に変換するために、線形合同法を使いました。英語版のWikipediaの記事を参考に、法が $2^{64}$ となる Knuth の式を採用しました。

  • $x_{n+1} = 6364136223846793005 x_n + 1442695040888963407$
  • $2^{64}$ を法として計算する。これは、unsigned long long で計算すれば、自然と実現できます。
  • 初期値は、大きな素数(1999000021)とした。

次に変換した値の累積和を求めて、2つの区間の和が一致しているか確かめます。このプログラムでは、部分集合の要素の数は確認していません(公式解説で紹介されているプログラムも同様でした)。

以下が、C++プログラムです。AC(Accepted=正しいプログラム)と判定されました。

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

typedef unsigned long long int ull;

int main()
{
  	int n, q;
	cin >> n >> q;
	vector<int> a(n);
	for (int i = 0; i < n; ++i) {
		cin >> a[i];
	}
	vector<int> b(n);
	for (int i = 0; i < n; ++i) {
		cin >> b[i];
	}

	vector<ull> h(n + 1);
	h[0] = 1999000021;
	for (int i = 0; i < n; ++i) {
		h[i + 1] = h[i] * 6364136223846793005ULL + 1442695040888963407ULL;
	}

	vector<ull> sum_a(n + 1, 0);
	vector<ull> sum_b(n + 1, 0);
	for (int i = 0; i < n; ++i) {
		sum_a[i + 1] = sum_a[i] + h[a[i]];
		sum_b[i + 1] = sum_b[i] + h[b[i]];
	}

	for (int i = 0; i < q; ++i) {
		int La, Ra, Lb, Rb;
		cin >> La >> Ra >> Lb >> Rb;
		--La;
		--Lb;
		if (sum_a[Ra] - sum_a[La] == sum_b[Rb] - sum_b[Lb]) {
			cout << "Yes" << endl;
		} else {
			cout << "No" << endl;
		}
	}

	return 0;
}

もう少し小さい範囲へのマッピングも試してみます。以下の $2^{32}$ を法とする以下の線形合同法を採用しました。

  • $x_{n+1} = 1664525 x_n + 1013904223$
  • $2^{32}$ を法として計算する。これは、unsigned int で計算すれば、自然と実現できます。
  • 初期値は、1 とした。

このプログラムでも AC と判定されました。

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

typedef unsigned int uint;

int main()
{
  	int n, q;
	cin >> n >> q;
	vector<int> a(n);
	for (int i = 0; i < n; ++i) {
		cin >> a[i];
	}
	vector<int> b(n);
	for (int i = 0; i < n; ++i) {
		cin >> b[i];
	}

	vector<uint> h(n + 1);
	h[0] = 1;
	for (int i = 0; i < n; ++i) {
		h[i + 1] = h[i] * 1664525U + 1013904223U;
	}

	vector<uint> sum_a(n + 1, 0);
	vector<uint> sum_b(n + 1, 0);
	for (int i = 0; i < n; ++i) {
		sum_a[i + 1] = sum_a[i] + h[a[i]];
		sum_b[i + 1] = sum_b[i] + h[b[i]];
	}

	for (int i = 0; i < q; ++i) {
		int La, Ra, Lb, Rb;
		cin >> La >> Ra >> Lb >> Rb;
		--La;
		--Lb;
		if (sum_a[Ra] - sum_a[La] == sum_b[Rb] - sum_b[Lb]) {
			cout << "Yes" << endl;
		} else {
			cout << "No" << endl;
		}
	}

	return 0;
}

Python プログラム例(ABC367F)

Python版も基本的な考え方は同じです。32ビット合同線形法のプログラムを移植しました。以下となります。

"""AtCoder Beginner Contest 367 F"""
MOD = 2 ** 32
n, q = map(int, input().split())
a = list(map(int, input().split()))
b = list(map(int, input().split()))

h = [0] * (n + 1)
h[0] = 1
for i in range(n):
    h[i + 1] = (h[i] * 1664525 + 1013904223) % MOD

sum_a = [0] * (n + 1)
sum_b = [0] * (n + 1)
for i in range(n):
    sum_a[i + 1] = (sum_a[i] + h[a[i]]) % MOD
    sum_b[i + 1] = (sum_b[i] + h[b[i]]) % MOD

for i in range(q):
    La, Ra, Lb, Rb = map(int, input().split())
    La -= 1
    Lb -= 1
    diff_a = (sum_a[Ra] - sum_a[La] + MOD) % MOD
    diff_b = (sum_b[Rb] - sum_b[Lb] + MOD) % MOD
    print("Yes" if diff_a == diff_b else "No")

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

最後に

大きな値の剰余を計算して値が同じであれば、同一視するという意味で、ABC339F問題解説記事)が類題となります。

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

COMMENT

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

CAPTCHA