公式

E - Sparse Range 解説 by kyopro_friends


原案:admin

この問題は尺取法により解くことができます。

\(L\) を固定した問題

\(L\) を固定したとき、どのような \(R\) が条件を満たすか考えます。

もし \((L,R)\) が条件を満たさないなら、ある \(i,j\) が存在して、\(L\leq i < j \leq R\) かつ \(|A_i-A_j|<D\) となります。この \((i,j)\) が反例となり、どのような\(\alpha \geq 1\) に対しても \((L,R+\alpha)\) は条件を満たしません。

すなわち、ある \(f(L)\) が存在し、\((L,L),(L,L+1),\dots,(L,f(L))\) は条件を満たし、\((L,f(L)+1),\dots,(L,N)\) は条件を満たさないことになります。

全ての \(L\) での \(f(L)\) を求める

\(f(L)\) がわかっているとき \(f(L+1)\) を求めることを考えます。\((L,f(L))\) が条件を満たすため、そこから対象となる組が減った \((L+1,f(L))\) も条件を満たします。即ち \(f(L)\leq f(L+1)\) であり、\(f(L+1)\) を求めるには \(f(L)\) の”続き”から確認をすれば十分です。よって、以下の擬似コードに示すような尺取法を行うことができます。

擬似コード

ans = 0
R = 1
for L in 1..N:
  while (L, R) が条件を満たす:
    R <- R+1
  //(L,L),...(L,R-1)が条件を満たす
  ans <- ans+(R-L)

判定問題を高速に解く

この尺取法が高速に動作するためには、判定問題「\((L,R)\) が条件を満たすか?」を高速に判定する必要があります。

\((L,R-1)\) が条件を満たすとき、\((L,R)\) が条件を満たすための必要十分条件は、全ての \(L \leq i < R\) に対して \(|A_i-A_R| \geq D\) となることです。このことは「\(A_L,\dots,A_{R-1}\) のうち \(A_R\) 以上の最小の要素が \(A_R+D\) 以上かつ、\(A_R\) 以下の最大の要素が \(A_R-D\) 以下であること」と同値です。

よって、以下ができるデータ構造があれば十分です

  • 要素の追加
  • 要素の削除
  • \(x\) 以上の最小の要素の取得
  • \(x\) 以下の最大の要素の取得

これらは ordered set を用いることでいずれも \(O(\log N)\) 時間で行えるため、\(O(N\log N)\) でこの問題が解けました。

実装例(C++)

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

int main(){
	int n,d;
	cin >> n >> d;
	vector<int>a(n);
	for(auto&x:a)cin >> x;
	
	ll ans=0;
	set<ll>s({(ll)-1e9,(ll)2e9});
	int r=0;
	for(int l=0;l<n;l++){
		while(r<n){
			auto it=s.lower_bound(a[r]);
			if(*it-a[r]<d)break;
			it--;
			if(a[r]-*it<d)break;
			s.insert(a[r]);
			r++;
		}
		ans+=r-l;
		s.erase(a[l]);
	}
	cout << ans << endl;
}

投稿日時:
最終更新: