公式

E - Cover query 解説 by mechanicalpenciI


白く塗られたマスからなる区間を順序付き集合で管理することを考えます。
例えば、マス \( 1,2,3,4,5, 8,9,10, 14\) のみが白く塗られている時、 \(\{[1,5], [8,10], [14,14]\}\) のような形で管理します。

最初の状態は \([1,N]\) です。 あるクエリの前の状態が、\(\{[l_1,r_1], [l_2,r_2], \ldots, [l_k,r_k]\}\) \((l_1\leq r_1<l_2\leq r_2<\cdots<l_k\leq r_k)\) であるとします。 ここにクエリとして \((L,R)\) (簡単のためクエリ番号を表す添字は省略します)が与えられたときの更新について考えます。
これは次のようになります。

  1. \(l_i\leq r_i<L\) のとき、その区間はそのまま残る。
  2. \(l_i<L\) かつ \(L\leq r_i\leq R\) のとき、区間 \([l_i,r_i]\) は削除され、代わりに区間 \([l_i,L-1]\) が追加される。
  3. \(l_i<L\) かつ \(R<r_i\) のとき、区間 \([l_i,r_i]\) は削除され、代わりに区間 \([l_i,L-1]\), \([R+1,r_i]\) が追加される。
  4. \(L\leq l_i \leq R\) かつ \(r_i\leq R\) のとき、区間 \([l_i,r_i]\) は削除される。
  5. \(L\leq l_i \leq R\) かつ \(R<r_i\) のとき、区間 \([l_i,r_i]\) は削除され、代わりに区間 \([R+1,r_i]\) が追加される。
  6. \(R<l_i\leq r_i\) のとき、その区間はそのまま残る。

ここで、\(2\) 番目のケースは各クエリについて高々 \(1\) つの \(i\) に対してしか起こり得ません。なぜなら、\(l_i<L\leq r_i\) をみたす \(i\) は高々 \(1\) つしかないためです。同様に\( 3,5\) 番目のケースも各クエリについて高々 \(1\) つの \(i\) に対してしか起こりません。
また、\(6\) つのケースの中で区間が増えるケースは \(3\) 番目のみのため、各クエリのたびに区間の数は高々 \(1\) つしか増えないこともわかります。

しかし、クエリごとにすべての区間について更新を行なっていると、最大で \(O(Q^2)\) の計算量がかかり、実行時間制限に間に合いません。
そこで、そのまま残る区間、すなわち、\(1,6\) 番目のケースについては更新を行わないことを考えます。ここで、\(j<i\) かつ \(r_i<L\) ならば \(r_j<L\) であり、同様に \(i<j\) かつ \(R<l_i\) ならば \(R<l_j\) となることに注意します。これより、\(2,3,4,5\) 番目のケースのいずれかに属する区間は整数の組 \((i_0,i_1)\) を用いて \([l_i,r_i]\) \((i_0\leq i\leq i_1)\) と書くことができることが分かります。(\(i<i_0\) のとき \([l_i,r_i]\)\(1\) 番目のケースに属し、\(i>i_1\) のとき \(6\) 番目のケースに属します。)
\(\{[l_1,r_1], [l_2,r_2], \ldots, [l_k,r_k]\}\) から \(L\leq r_i\) をみたす最小の \(i\) を取得することは適切な関数 (c++であれば std::set に対する lower_bound, python であれば SortedSet に対する bisect_left 等)で \(O(\log k)\) で行うことができるため、その後 \(l_i\leq R\) である限り、\(i\) をインクリメントし、一度 \(R<l_i\) となったら操作を終了することで、必要な区間(\(2,3,4,5\) 番目のケースのいずれかに属する区間)にのみ更新を行うことができます。
さて、このように操作を行った時の操作回数について考えます。先に述べたように \(2,3,5\) 番目のケースは各クエリにおいて高々 \(1\) 回しか行われません。一方で \(4\) 番目のケースは行われるたびに区間の数が \(1\) ずつ減少するため、先に述べた「各クエリのたびに区間の数は高々 \(1\) つしか増加しない」という事実と合わせて、\(Q\) 個のクエリ合計で高々 \(Q+1\) 回(実際には高々 \(Q\) 回)しか行われないことが分かります。よって、操作回数はすべてのクエリで合わせて高々 \(O(Q)\) 回であり、検索, 挿入, 削除等は高々 \(O(\log Q)\) で行える(集合のサイズが高々 \(Q\) であることに注意)ことから、全体で \(O(Q\log Q)\) でこの問題を解くことができます。これは本問題の制約下で十分高速です。
実際の出力については、白いマスの個数の合計を別の変数で持っておき、区間を更新するたびにあわせて更新しつつ、各クエリが終わるたびに出力すれば良いです。

なお、(以下の実装例では採用していませんが、)実装時にはマス番号ではなく、マス \(i\) が区間 \([i,i+1)\) を覆っていると考えて、扱うと添字等の管理が楽になる場合があります。

c++ による実装例:

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

int main() {
	int n,q,L,R;
	set<pair<int,int> >st; //(r_i,l_i)
	set<pair<int,int> >::iterator itr;

	cin>>n>>q;
	st.insert({n,1});
	int ans=n;
	for(int i=0;i<q;i++){
		cin>>L>>R;
		itr=st.lower_bound({L,-1}); //the smallest i s.t. L<=r_i 
		vector<pair<int,int> >resv;
		while(itr!=st.end()){
			int sl=(*itr).second;
			int sr=(*itr).first;
			if(R<sl)break;
			if((sl<L)&&(R<sr)){
				resv.push_back({L-1,sl});
				resv.push_back({sr,R+1});
			}
			else if((sl<L)&&(L<=sr)){
				resv.push_back({L-1,sl});
			}
			else if((R<sr)&&(sl<=R)){
				resv.push_back({sr,R+1});
			}
			ans-=min(sr,R)-max(sl,L)+1;
			itr=st.erase(itr);
		}
		int sz =resv.size();
		for(int i=0;i<sz;i++){
			st.insert(resv[i]);
		}
		cout<<ans<<endl;
	}

	return 0;
}

投稿日時:
最終更新: