「アルゴリズム コンペティション: クイック 300 問」は 2024 年に出版される予定で、「アルゴリズム コンペティション」の補助問題集です。
すべての質問は、自作の OJ New Online Judgeに配置されます。
コードは C/C++、Java、Python の 3 つの言語で提供されており、トピックは主に中レベルから低レベルのトピックであり、初級レベルから上級レベルの学生に適しています。
「 水 」、リンク: http://oj.ecustacm.cn/problem.php?id=1902
質問の説明
[問題の説明] N 個の水滴の座標が与えられ、y は水滴の高さ、x は水滴が落ちる位置を x 軸に表します。
水滴は 1 秒あたり 1 単位の長さの速度で落下します。
植木鉢が受けた最初の水滴と植木鉢が受けた最後の水滴の終わりとの間の時間差が少なくとも D になるような、x 軸上の位置に植木鉢を配置する必要があります。
水滴が x 軸上に落ちているか、植木鉢の端に揃っているか、植木鉢の中にある限り、水滴は捕らえられたと見なされます。
滴る水の座標 N とサイズ D を考慮して、最小の植木鉢の幅 W を計算してください。
[入力形式] 1 行目: 2 つの整数 N と D、1 <= N <= 100,000、1 <= D <= 1,000,000。
次の N 行: 各行には、雨滴の座標を表す 2 つの整数 x と y が含まれます (0 <= x、y <= 1,000,000)。
[出力形式]最小の植木鉢の幅を示す整数を 1 行に 1 つだけ表示します。
D 単位時間内に必要な水滴をキャッチするのに十分な幅の植木鉢を構築することが不可能な場合は、-1 を出力します。
【入力サンプル】
4 5
6 3
2 4
4 10
12 15
【出力サンプル】
2
答え
質問の意味が少しわかりにくいので、例を挙げて説明します。水滴は n = 4 個あります。水滴を受ける場所に植木鉢を置きます。最初の水滴を受け取ってから最後の水滴まで、植木鉢にすべての水滴が落ちるまでに要する時間の合計は d = を超えます。 6秒。植木鉢の最小幅はどれくらいか聞いてください。答えは、最初の水ドロップ(6, 3)と3番目の水ドロップ(4, 10)を選択し、それらが植木鉢に落ちるまでの時間差は10 - 3 = 7です。6秒を超えると幅が広がります。植木鉢の数は6 - 4 = 2です。
以下に質問の意味を概説します。任意の区間(植木鉢の幅)[L,R]を選択し、その区間における最大値と最小値(最も高い水滴と最も低い水滴)の差を数え、≧dの場合を法定区間といい、そして間隔幅が記録されます。このような法的間隔をすべてたどり、最小幅を見つけます。これが答えです。
区間問題の場合、ルーラー法を使用し、高速ポインターと低速ポインターによって形成される「スライディング ウィンドウ」を使用してすべての区間を横断することができ、計算の複雑さは O ( n 2 ) O (n^2) です。O ( n2 )、タイムアウト。この質問には少なくともO (nlogn) O(nlogn)複雑さO ( n log n )のアルゴリズム。
ルーラー方法に加えて、スライディング ウィンドウを使用して間隔を横断する単調キューもあります。「アルゴリズムコンペティション」の 10 ページに同様の質問例「Luogu P1886」があります固定のウィンドウ幅 k が与えられると、ウィンドウ幅が k に等しいすべての間隔で最大値と最小値を出力する必要があります。単調キューで解決すると、複雑さはわずかO ( n ) O(n)O ( n )。このセクションを注意深く読んで、複雑さがO ( n ) O(n)である理由を理解してください。O ( n ) ._
この質問は、ウィンドウ内の最大値と最小値を求める点では Luogu P1886 と同じですが、違いは、この質問が固定のウィンドウ幅を与えないことです。次に、二分法を使用して最小の k を推測します。毎回ウィンドウ幅 k を推測し、関数 check(k) を使用してウィンドウ幅が k のときに正当な間隔があるかどうかを判断します。関数 check() は単調関数を使用します。問題を解決するために待ちます。「二分法+単調待ち行列」の計算量、バイナリ法推測O(logn) O(logn)O ( l o g n )回、check() を使用して幅 k のすべての間隔の最大値と最小値を見つけるたびに、O ( n ) O(n) になります。O ( n )、合計複雑さO ( nlogn ) O(nlogn)O ( nlogn ) . _ _ _ _
コード内の単調キューは手書きです (手書きのキューについては、清華大学出版局、Luo Yongjun および Guo Weibin 著の「Algorithm Competition」、7 ページを参照してください)。h はキューの先頭、t はキューの末尾です。h ≤ t を維持すると、キューの長さは t - h + 1 に等しくなります。h++ はキューの先頭をポップ (削除) することを意味し、t– はポップを意味しますキューの最後尾を(削除)します。
関数 check(k) の機能は、幅 k の正当なウィンドウが存在し、このウィンドウ内の最大値と最小値の差が ≥ d であるかどうかをチェックすることです。2 つの単調キューを使用して、ウィンドウ内の最大値と最小値をそれぞれ見つけます。q1 は単調増加キューでキューの先頭が最小値、q2 は単調減少キューでキューの先頭が最大値です。
【ポイント】モノトーンのキュー。
C++ コード
#include<bits/stdc++.h>
using namespace std;
const int N=100001;
int n,d;
int q1[N],q2[N]; //q1队头是窗口内最小值,q2队头是窗口内最大值
struct node{
int x,y;}a[N];
int cmp(node u,node v){
return u.x<v.x;}
bool check(int k){
memset(q1,0,sizeof(q1)); //单调递增,队头最小
memset(q2,0,sizeof(q2)); //单调递减,队头最大
int h1=1,t1=0,h2=1,t2=0; //h:队头,t队尾 ,注意保持 h<=t
for(int i=1;i<=n;i++){
//a[i]一个个地从队尾进入
while(h1<=t1 && a[q1[h1]].x < a[i].x-k) h1++; //窗口宽度大于k了,弹走队头,减小到k
while(h1<=t1 && a[i].y < a[q1[t1]].y)
t1--; //如果原队尾比a[i].y更大,删除队尾。保持队头a[q1[h1]].y最小
q1[++t1]=i; //现在队内都比a[i]小了,a[i]从队尾进队
//前面几行求窗口内的最小值,下面几行求窗口内的最大值
while(h2<=t2 && a[q2[h2]].x < a[i].x-k) h2++; //窗口宽度大于k了,弹走队头,减小到k
while(h2<=t2 && a[i].y > a[q2[t2]].y)
t2--; //如果原队尾比a[i].y更小,删除队尾。保持队头a[q2[h2]].y最大
q2[++t2]=i; //现在队内都比a[i]大了,a[i]从队尾进队
if(a[q2[h2]].y-a[q1[h1]].y >= d) return true; //窗口内最大值最小值之差大于等于d
}
return false;
}
int main(){
cin>>n>>d;
for(int i=1;i<=n;i++) scanf("%d%d",&a[i].x,&a[i].y);
sort(a+1,a+n+1,cmp); // 根据x值升序
int L=1,R=1e6,ans=-1;
while(L<=R){
//二分求最小宽度
int mid=(L+R)>>1;
if(check(mid)) ans=mid,R=mid-1;
else L=mid+1;
}
cout<<ans<<endl;
return 0;
}
Javaコード
import java.util.*;
import java.io.*;
class Main {
static class Node {
int x, y;
Node(int x, int y) {
this.x = x;
this.y = y;
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int d = scanner.nextInt();
Node[] a = new Node[n + 1];
for (int i = 1; i <= n; i++) {
int x = scanner.nextInt();
int y = scanner.nextInt();
a[i] = new Node(x, y);
}
Arrays.sort(a, 1, n + 1, (u, v) -> u.x - v.x);
int L = 1, R = 1000000, ans = -1;
while (L <= R) {
int mid = (L + R) >> 1;
if (check(a, n, d, mid)) {
ans = mid;
R = mid - 1;
} else {
L = mid + 1;
}
}
System.out.println(ans);
}
static boolean check(Node[] a, int n, int d, int k) {
int[] q1 = new int[n+10];
int[] q2 = new int[n+10];
int h1 = 1, t1 = 0, h2 = 1, t2 = 0;
for (int i = 1; i <= n; i++) {
while (h1 <= t1 && a[q1[h1]].x < a[i].x - k)
h1++;
while (h1 <= t1 && a[i].y < a[q1[t1]].y)
t1--;
q1[++t1] = i;
while (h2 <= t2 && a[q2[h2]].x < a[i].x - k)
h2++;
while (h2 <= t2 && a[i].y > a[q2[t2]].y)
t2--;
q2[++t2] = i;
if (a[q2[h2]].y - a[q1[h1]].y >= d)
return true;
}
return false;
}
}
Pythonコード
#pypy
import sys
input = sys.stdin.readline
def check(a, n, d, k):
q1 = [0] * (n + 1)
q2 = [0] * (n + 1)
h1, t1, h2, t2 = 1, 0, 1, 0
for i in range(1, n + 1):
while h1 <= t1 and a[q1[h1]][0] < a[i][0] - k: h1 += 1
while h1 <= t1 and a[i][1] < a[q1[t1]][1]: t1 -= 1
t1 += 1
q1[t1] = i
while h2 <= t2 and a[q2[h2]][0] < a[i][0] - k: h2 += 1
while h2 <= t2 and a[i][1] > a[q2[t2]][1]: t2 -= 1
t2 += 1
q2[t2] = i
if a[q2[h2]][1] - a[q1[h1]][1] >= d: return True
return False
n, d = map(int, input().split())
a = [(0,0)]
for _ in range(n):
x, y = map(int, input().split())
a.append((x, y))
a.sort()
L, R, ans = 1, 1000000, -1
while L <= R:
mid = (L + R) >> 1
if check(a, n, d, mid):
ans = mid
R = mid - 1
else: L = mid + 1
print(ans)