コンテスト中、Sparse Table だと思わずに解いていた。コンテスト後に TL で Sparse Table だと見て、「確かに!」と思った。
問題概要
インタラクティブ問題である。次のフェーズ I とフェーズ II に分かれる。
フェーズ I
ジャッジから整数 が与えられるので、それに応じて、あなたは 1 以上 50000 以下の整数 を出力する。
さらにあなたは、各 について を満たす、 個の整数の組 を出力する。
フェーズ II
ジャッジから 回のクエリが与えられる。各クエリでは 2 つの整数 が与えられる。
それに対して、あなたは 1 以上 以下の 2 つの整数 を、次の条件を満たすように出力する ( でもよい)。
「集合 {} と集合 {} の和集合が、集合 {} と一致する。
制約
考えたこと
ここでは、Sparse Table をまったく知らなくてもアドホックに解ける思考過程を示す。
まず、この問題はインタラクティブな見た目をしているが、実質的には構築問題だといえる。つまり、整数 に応じて、次の条件を満たすような区間の集合を求めよ、ということだ。
もし の大きさに上限がないならば、 個の区間をすべて出力すれば十分だ。実際には、許される区間の個数は、オーダー的には 程度だ。なお、求める区間の集合を「解集合」と呼ぶことにする。
順に考えていく
まずは長さが 1 の区間をカバーしていくことを考える。つまり、 という場合だ。こればかりは、すべて解集合に含める必要があるだろう。つまり、
をすべて解集合に含めることにする。
これらの解集合から 2 つの区間を選んで和集合をとることによって、 といった、長さ 2 の区間もすべて実現できる。そこで今度は、長さが 3 の区間を解集合に含めていくことにする。つまり、
を解集合に含めていくことにする。このとき、長さ 6 以下の区間はすべて実現できることになる。たとえば
といった具合だ。このノリで、
と続けていくことで、すべての長さの区間が実現できることになる。
区間の長さが指数的に増加していることから、最終的な解集合における区間の個数は のオーダーの個数になる。よって大丈夫。
なお、以上の考察は、Sparse Table をまったく知らなくても十分アドホックに導出できると思う。
余談:Sparse Table
Sparse Table を踏まえると、区間の長さの系列を とするよりは、 とする方が自然だ。それでももちろん正解になる。
Sparse Table の内容については、公式解説に言及がある。
コード
#include <bits/stdc++.h> using namespace std; using pint = pair<int, int>; int main() { // フェーズ I int N; cin >> N; vector<pint> res; // 答え map<pint, int> ma; // 区間 -> 区間番号 int len = 1; while (len <= N) { for (int i = 1; i + len - 1 <= N; ++i) { res.emplace_back(i, i + len - 1); ma[pint(i, i + len - 1)] = res.size(); } len = len * 2 + 1; } cout << res.size() << endl; for (int i = 0; i < res.size(); ++i) cout << res[i].first << " " << res[i].second << endl; // フェーズ II int Q; cin >> Q; while (Q--) { int u, v; cin >> u >> v; len = 1; while (len <= v - u + 1) len = len * 2 + 1; len /= 2; cout << ma[pint(u, u + len - 1)] << " " << ma[pint(v - len + 1, v)] << endl; } }