淺析拯救小矮人的 nlogn 算法及其證明
題型簡介:
有 $ n $ 個人,第 $ i $ 個人身高 $ a_i $ 手長 $ b_i $ ,他們為了從一個高為 $ H $ 的洞中出去,決定搭人梯。如果一個人和他下面的人的身高之和加上他的手長可以達到洞的高度,那么他就可以出去。求最多有多少人能出去。 $ n\leq 10^6 $
算法流程
本題需要貪心,所以我們可以貪心到底。首先我們將所有人,按照他們的最低逃生高度 $ H-a_i-b_i $ 從高到低排序。一個必須要知道的結論:最低逃生高度越高的人,一定越先走。
首先我們將所有人按照 $ H-a_i-b_i $ (最低逃生高度)從高到低排序,根據結論越高的人越先走。然后如果是 $ n^2 $ 背包,就是對每個人做有條件的背包,模擬每個人是否能走。
而 $ n\times logn $ 的方法則是記錄一個后綴 $ s[] $ ,其中 $ s[i] $ 表示第 $ i $ 個人后面最低逃生高度比他低的所有人的身高總和,然后用一個 $ tot $ 記錄前面沒有出去的人的身高總和,對於從高到低枚舉的第 $ i $ 個人,如果 $ s[i]+tot>=H-a_i-b_i $ 就說明他能出去(於是默認他出去);否則就將這個人的身高和前面所有已經出去的人的身高作比較,如果當前這個人最高那么他就不出去了,不然就從前面已經出去的人里面找到那個最高的人,把他拉回洞里墊在下面,讓當前這個人出去!照着這個過程做我們就能得到最優解。
算法證明:
其實這個算法只有兩個待考究的地方,問題一:為什么最低逃生高度高的人,一定越先走?這個問題在很多題解里已經討論過了,難以講清,本題不做多講,就用一張圖感性一下:
本算法第二個問題在於這句話: 否則就將這個人的身高和前面所有已經出去的人的身高作比較,如果當前這個人最高那么他就不出去了,墊到下面去;不然就從前面已經出去的人里面找到那個最高的人,把他拉回洞里墊在下面,讓當前這個人出去!為什么把上面最高的那個人拉下來,這個人就一定可以出去了?為什么只取一個人下來,我們可不可以拉多個人下來,讓當前這個人出去的同時為后面的人墊高度?這個我們用兩張圖解讀:
$ code: $
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#define ll long long
#define db double
#define rg register int
using namespace std;
int n,H; //人數;陷阱高度
int tot,ans; //之前沒能逃生的人的身高總和;答案
int s[200005]; // s[i]表示在第i個人后面逃生的所有人的身高總和,就是后綴和
priority_queue<int> q; //用來求之前已經逃生的人中,身高最高的人
struct su{
int a,b,h,id; //身高,手長,最低逃生高度,編號
inline bool operator <(const su &i)const{
return h>i.h; //按最低逃生高度,從高到底排序
} //(其實說白了就是逃生能力排序,為了方便理解就詳細一點)
}p[200005]; //存小矮人信息的數組 people
inline int qr(){ //快讀
register char ch; register bool sign=0; rg res=0;
while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
if(sign)return -res; else return res;
}
int main(){
n=qr();
for(rg i=1;i<=n;++i)
p[i].a=qr(),p[i].b=qr();
H=qr();
for(rg i=1;i<=n;++i){
p[i].h=H-p[i].a-p[i].b; //最低逃生高度
p[i].id=i; //每個人的編號
}
sort(p+1,p+n+1);
for(rg i=n;i>=1;--i)
s[i]=s[i+1]+p[i+1].a; //s[i]表示在第i個人后面逃生的所有人堆起來達到的高度
for(rg i=1;i<=n;++i){
if(tot+s[i]>=p[i].h) q.push(p[i].a), ++ans;
//如果在他前面不能逃生的人加上在他后面的人堆起來達到了他的最低逃生高度,就讓他走
else{
if(!q.empty()&&q.top()>p[i].a){ //拿出之前最大的身高和他對比,較大的走
tot+=q.top();
q.pop(); --ans;
q.push(p[i].a); ++ans; //其實ans根本沒變,為了高度還原就這樣了
} else tot+=p[i].a; //否則這個就墊到下面去
}
}printf("%d\n",ans);
return 0;
}