大家好,本來今天想寫一篇演算法和資料結構的。但是看了一眼計劃,發現基本上大部分基礎的內容都已經講過了。接下去就是一些競賽相關的演算法了,剛好最近是校招季,所以寫一點筆試題的題解,也許對大家的招聘有點用。
這一次選了拼多多的校招筆試題其中的一題,在寫文章的時候還看到了小馬智行的。也就是那個樓教主創辦的著名的pony.ai,但是我點進去看了一眼,發現大部分都是acm競賽題的風格,難度對於普通學生而言有些高了。所以沒有采納,改選了拼多多的試題。
題意
給定一個整數N,代表N個盒子。第i個盒子當中有i個球。
我們可以選定一個N以內的自然數X,多多雞會把所有盒中小球數量大於X的盒子減少X個球。現在想要用最少的步驟將所有盒子的球清空,請問最少需要多少次操作?
樣例
第一行輸入一個整數t,表示測試組數。
對於每一行都輸入一個整數N()
要求對於每組資料輸出一個整數作為結果。
分析
我們仔細分析一下,會發現這題的難點有兩個。第一個是這個N的範圍太大了,對我們的複雜度限制得很高。第二點是盒子當中球的數量是動態的,在如此苛刻的複雜度要求下,我們很難掌握所有盒子的動態。
但如果你有足夠多經驗的話,會發現N的範圍其實並不是限制而是提示。N的範圍達到1e9,在這個量級下我們連的計算都是會超時的,也就是說所有需要遍歷盒子的演算法都可以放棄了,看似苛刻,其實會節省我們很多時間。如果N的範圍給個1e6,那才是真的噁心。估計很多同學要被騙了,苦苦思考怎麼樣通過模擬的方法來計算。
既然範圍是1e9,那麼沒的說,這題一定是通過一些巧妙的方法來計算的。但是究竟是什麼巧妙的方法,我們幹想是想不出來的,要想知道也不難,嘗試著去做一下就可以找到門道了。
我們假設我們第一次選擇了k,也就是序號大於等於k的盒子裡球的數量都減少了k。那麼減少之後的情況變成什麼樣了呢?我們列出來看看:。
有些同學看到這個可能會想第二個數字選什麼,如果你這麼想了,可能你做的題目還不夠多,不夠敏感。其實看到這個已經可以發現,當我們選擇了k之後,陣列被拆分成了兩個部分,左邊是0到k-1,右邊是1到N-k,中間0是分割線。
這一點發現有什麼用呢?其實很有用,我們首先來做一個假設,假設k-1 > N-k,也就是左邊部分的元素比右邊更多。那麼不管我們接下來如何操作,其實只要我們的操作能夠消除掉左邊的部分,右邊的自然也會跟著消除。同理,如果k-1 < N-k,也是一樣的。所以我們通過選擇了k之後,陣列拆分成了兩個部分,答案只和其中的一個部分有關,並且是和其中元素最多的部分有關。
那麼根據這一點,我們可以直接寫出表示式來表示N時的答案:
這個式子看起來很複雜不知道如何解,但其實也很簡單,我們還有一個條件沒有用上。就是f必然是一個遞增函式,這個其實不需要嚴格證明,我們直觀上就可以感受出來。既然f是遞增函式,那麼上面式子當中很多元素的大小關係就都明顯了。
這樣遞推式就出來了,我們接下來要做的就是根據這個遞推式寫出它的通項。
我們把上面的式子全部累加在一起,右邊帶有f的項會被全部消掉,最終得到:。這個表示式有了,那麼程式碼自然手到擒來。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <cmath>
#include <cstdlib>
#include <string>
#include <map>
#include <set>
#include <algorithm>
#include "time.h"
#include <functional>
#define rep(i,a,b) for (int i=a;i<b;i++)
#define Rep(i,a,b) for (int i=a;i>=b;i--)
#define foreach(e,x) for (__typeof(x.begin()) e=x.begin();e!=x.end();e++)
#define mid ((l+r)>>1)
#define lson (k<<1)
#define rson (k<<1|1)
#define MEM(a,x) memset(a,x,sizeof a)
#define L ch[r][0]
#define R ch[r][1]
using namespace std;
const int N=1000050;
const long long Mod=1000000007;
int t, x;
int main() {
scanf("%d", &t);
rep(z, 0, t) {
scanf("%d", &x);
printf("%d\n", int(log(x)/log(2)) + 1);
}
return 0;
}
感想
這道題從難度上來講其實不大,但是真正在筆試的過程當中遇到,估計很多同學可能做不出來。倒不是因為演算法有多難,而是會一開始的時候就走了歪路,比如去思考怎麼樣選擇k,比如去想遞推的解法等等。這種對問題的敏感和思路是需要練習的,並不是看幾篇文章或者是聽聽大牛講課就可以獲得的。
一般公司的筆試題不會很難,往往都是這種需要縝密思考的思維題,這種題多做多練很容易就摸到套路了。如果對這些問題感興趣可以看看codeforces專題,裡面有很多有趣的思維題。
今天的文章就到這裡,衷心祝願大家每天都有所收穫。如果還喜歡今天的內容的話,請來一個三連支援吧~(點贊、關注、轉發)
{{uploading-image-72342.png(uploading...)}}