1. 程式人生 > 實用技巧 >【GOJ 3038】逛餐館

【GOJ 3038】逛餐館

傳送門

解析

顯然我們只會往一個方向走,不會回頭(回頭一定浪費時間)。那我們將所有餐館序。

那我們不妨列舉我們能走到最遠的餐館,假設我們列舉我們走到第 \(i\)個餐館(排完序之後)。
那麼我們就是在 \(1\) ~ \(i\) 餐館中尋若干個餐館,使他們和不超過 \(m-x_i\),然後求最多的個數即可。那顯然就是在 \(1\) ~ \(i\) 餐館中按照 \(t_i\) 從小到大排序,然後不斷累加直至超過 \(m-x_i\) 的餐館個數。

這個經典題目用優先佇列來維護即可。

我們維護一個大根堆,這個堆裡面維護 \(1\) ~ \(i\) 所有餐館中,最小的若干個餐館,並且他們和 \(\le m-x_i\)

那我們從 \(1\)\(n\) 列舉 \(i\) ,每次執行三個操作:

  1. \(t_i\) 加進堆裡面。
  2. 若堆中元素之和大於 \(m-x_i\),則不斷彈出堆頂元素直到總和小於等於 \(m-x_i\)
  3. 堆的大小就是我們能吃的餐館的個數,用這個更新答案。

時間複雜度為 \(O(n\ \log_2n)\)

程式碼

#include<bits/stdc++.h>
using namespace std;
using LL=long long;
const LL N=1e5+5;
struct jvav {
	LL x,t;
	void read() {
		scanf("%lld %lld",&x,&t);
	}
	bool operator<(const jvav &bee) const {
		return x<bee.x;
	}
} a[N];
priority_queue<LL> q;
int main() {
	LL n,m;
	scanf("%lld %lld",&n,&m);
	for(LL i=1; i<=n; i++) {
		a[i].read();
	}
	sort(a+1,a+n+1);
	LL sum=0,ans=0;
	for(LL i=1; i<=n; i++) {
		q.push(a[i].t),sum+=a[i].t;
		while(q.size()&&sum>m-a[i].x) {
			sum-=q.top(),q.pop();
		}
		if(ans<q.size()) {
			ans=q.size();
		}
	}
	printf("%lld",ans);
	return 0;
}