今天你要在一疊履歷中錄取一個祕書,但由於你很懶惰,所以在看每一個履歷的當下你就得決定要不要錄取,決定不錄取就不能再回頭錄取他了,請問你的最佳策略是?
具體來說,給你\(n\)個數字,強迫在線,複雜度\(O(n)\),盡量選出最好的
捨棄前\(k=\frac{n}{e}\)個,拿接下來第一個比前\(k\)個好的,若一直都沒有遇到的話只能拿最後一個
(講義中沒有提到,但這比較重要)
若選擇捨棄前\(r\)個,則取得第一名的機率為
\(P(r)=\sum_{i=r+1}^{n}\frac{r}{i-1}\times\frac{1}{n}=\frac{r}{n}\sum_{i=r+1}^{n}\frac{1}{i-1}\)
\(let\ x=\lim_{n \to \infty}\frac{r}{n} , t=\frac{i}{n}\)
原式近似於\(P(x)=x\int_{x}^{1}\frac{1}{t}dt=-x\ln(x)\)
\(P^{\prime}(x)=0\Rightarrow x=\frac{1}{e}\)
帶回去即可得到\(r=\frac{n}{e}\),大功告成!
這個策略是讓你獲得第一名的那個的機率最高的方法,如果考慮第二名獲第三名的話,策略會有所改變
int A[n]=data;
int mx=0;
int ans=0;
for(int i=0;i<n;i++){
if(i<n/e) mx=max(mx,A[i]);
else if(A[i]>mx){
ans=A[i];
break;
}
if(i==n-1) ans=A[i];
}
某些機率的問題與這個問題等價,此時你就知道怎麼做了
但不建議用這個方法選秘書啦,當然是選身材
我現在心裡有一個0~10000的數字,你猜我就會回答你大一點或小一點,你要猜幾次才猜的到?
每次猜目前可能區間的中間值,總次數\(O(\log n)\)
補充:可以把\(O(f(n))\)想成忽略常數
int ans=???;
int binary_search(int n){
int l=0,r=n;//[0,n)
while(true){
if(r-l<=1) return l;
int mid=(l+r)/2;
if(ans<mid) r=mid;
else l=mid;
}
}
應用環境:具有單調性
程式設計中極為常用的演算法
甚至還有令人瞠目結舌的整體二分、WQS二分搜等怪東西
今天培胖在錄一個報告,一開始的時候他的麥克風是拿在手上,比較靠近他的嘴巴,所以比較大聲,但後來手就痠了,所以就把麥克風放下來,聲音也變小了。你是幫他剪影片的胖辰,你想幫他找到他放下麥克風的位置,然後調大音量,你該怎麼做比較快?
二分搜辣個位置!!
你有一個長度為\(n\)的數列,他是\(1\)到\(n\)的一種排列,你想要找到任意一個\(a_i\)滿足\(a_i<a_{i+1} \&\& a_i<a_{i-1}\),或稱區域最小值
詢問次數\(2\log_2 n\)左右
假設目前區間\([l,r]\),中點為\(m\),每次詢問\(a_m\)跟\(a_{m+1}\),
\(a_m>a_{m+1}\),區間\([m,r]\)中必有區域最小值
\(a_m<a_{m+1}\),區間\([l,m+1]\)中必有區域最小值
你今天想在一個毫無規則亂的一堆名片中找到隔壁便當店的名片來叫外送,你該怎麼辦?
一個一個找阿,誰叫你不好好收
全部找過一遍找到為止!!!
超慢!快餓死了!
假如你想好好收好那些名片,稍微好一點的方法是......
把常用到的放在上面嘛!
這樣下次拿的時候不就很快就拿到了
語言學家齊夫發現,
最常出現的第\(i\)名的單字出現的機率大約成正比於\(\frac{1}{i}\)
若總共有\(n\)個,可以寫成
\(P(i)=\frac{1}{i}\frac{1}{H_n}\)
其中\(H_n=\sum_{i=1}^{n}\frac{1}{i}\)
是不是跟你用到隔壁便當店的名片跟賣保險的名片的機率分布很像?
各種情況常常都符合冪次法則,比較通用的形式為
\(P(X=x)\propto cx^{-k}\)
至於次方\(-k\)可以透過取對數作圖看斜率得到
在數學上還有班佛定律:在一個資料集中,開頭為1的數字佔30%!
回到前面的問題,所以到底怎麼樣放名片會最好?
當然我知道資料結構有很多種,但我們這邊講一些簡單的
在一串資料中從頭開始找東西,找到就把目前找的東西移到最前面!
在資料符合類似齊夫定律的分布之下,期望複雜度為\(O(\frac{n}{\log n})\)
假設他已經排好,搜尋期望次數為\(\sum_{i=1}^{n}i \times P(i)\),還記得\(P(i)=\frac{1}{i}\frac{1}{H_n}\),所以其實期望次數為\(\frac{n}{H_n}\approx \frac{n}{\log n}\),至於調和級數近似於\(\log n\)這裡就不再贅述。
勾勾有一個電話簿,上面記載著每一個跟他約會過的女生的電話,因為他實在是太帥又太有魅力了,以至於他跟妹子約會的的時候甚至不用主動開口就能要到電話。
突然有一天,勾勾想通了。一直不斷的換妹也不是辦法,他約過的這麼多女生中總有一個符合他的胃口的吧!所以他決定開始"回味"他之前約過的妹。
但是他實在是太會約了,以致於他的電話簿大到含不......我是說要找很久啦。所以勾勾現在決定請你幫他一把。
由於勾勾對於某些被他"回味"過的妹特別感興趣,就會多詢問幾次。
因此你可以假設測資符合齊夫定律(Zipf's law),並且使用前移法(Move-to-the-front method)等方式通過本題。
struct person{
string name,num;
person *prev=nullptr,*next;
person(string _name,string _num,person *_next):name(_name),num(_num),next(_next) {}
};
int main(){
int n;cin >> n;
person *first=nullptr;
for(int i=0;i<n;i++){
string name,num;
cin >> name >> num;
if(!first) first=new person(name,num,nullptr);
else{
person *now=new person(name,num,first);
first=now;
first->next->prev=first;
}
}
int q;cin >> q;
while(q--){
string name;cin >> name;
person *now=first;
string ans="7122";
while(now){
if(now->name==name){
ans=now->num;
break;
}
now=now->next;
}
if(ans!="7122"&&now!=first){
person *tem=first->next;
first->prev=now->prev;
first->next=now->next;
if(first->prev) first->prev->next=first;
if(first->next) first->next->prev=first;
now->prev=nullptr;
now->next=tem;
first=now;
}
cout << ans << '\n';
}
return 0;
}
謝謝大家