快手2020校園招聘秋招筆試–算法C試卷 解題報告 Apare_xzc
2020/4/10
網頁鏈接:牛客鏈接
題型分佈:
選擇題(2分/道*20道)
編程題(15分/道*4道)
選擇題中的知識點學習回顧:
線性迴歸中的殘差服從均值(期望)爲0的高斯分佈(正態分佈)。
一次不定方程解的個數:m個盒子放入n個小球
盒子非空:插板法:C(n-1,m-1)
盒子可空:先轉化爲等價非空:C(m+n-1,m-1)
直線切分平面:n*(n+1)/2+1
平面分割空間:(n^3+5n+6)/6
編程題有4道
21. 運動會
輸入例子1
3
3 10
1 5
4 6
輸出例子1
1
分析:
加油的時長爲(ed-st)/2+1, 每個節目我們可以計算出最少加油時長和最晚開始時間。
按照最晚開始時間排序,然後貪心檢查
代碼:
#include <bits/stdc++.h>
using namespace std;
struct Node{
int st,ed,x,t; //最遲開始時間
void getx() {
t = (ed-st)/2+1;
x = t+st;
}
bool operator < (const Node& rhs) const {
return x < rhs.x;
}
}node[20];
int main(void) {
int n;
cin>>n;
for(int i=0;i<n;++i)
scanf("%d%d",&node[i].st,&node[i].ed),node[i].getx();
sort(node,node+n);
int p = node[0].st+node[0].t;
bool ok = true;
for(int i=1;i<n;++i) {
if(p>node[i].x) {
ok = false;break;
}
p = p+node[i].t;
}
if(ok) puts("1");
else puts("-1");
return 0;
}
22. 小遊戲
有位老鐵設計了一個跳格子游戲,遊戲有N個格子順序排成一行,編號從1到N,每個格子有點數Qi,有標記Li(標記的範圍是1-M),每次跳格子,要選擇一個格子a,以任意正偶數距離x跳到格子b,如果格子b在遊戲區域內,且La=Lb,則稱爲一次合法跳躍,獲得的分數是(a + b) * (Qa + Qb)。
在繼續設計遊戲玩法時,這位老鐵糾結了很久,於是他決定放棄……但是他想知道所有合法跳躍總共能獲得多少分。
數據範圍:
這題不給數據範圍,交上去RE的RE, TLE的TLE, 試了幾次才試出來…
n<=1E5
m<=1E4
分析:
每次只能跳到相距偶數個的,Lb相同的格子。開始不知道數據範圍,寫了一發n^2的暴力,結果T了50%的數據。
我們把可以互相轉移的格子分到一組裏,這樣的話,在組內,不同的格子之間兩兩都對分數有貢獻。我們分組的時候奇數格子和偶數格子要分開。
如果x和y可以相互到達,那麼(x,y)這對位置對於分數的貢獻就是(x+y)*(Qx+Qy) = x*Qx + y*Qx + y*Qy + x*Qy
。我們可以計算一下Qx對於答案的貢獻。如果組內有m個元素,那麼x就要和m-1個位置相互可達。m-1個位置每個貢獻都有xQx, 每個y都有yQx。所以Qx的貢獻就爲x * Qx * (m-1) + Qx * (Qy1 + Qy2 + ... + Qym-1)
, 我們可以維護組內Q值的和sum。這樣的話Qx的貢獻就是x * Qx + Qx * (sum - Qx)
。
我們可以開兩個數組分別記錄一下每個標誌位L的組元素的個數與Q的和,然後計算即可。
代碼:
#include <bits/stdc++.h>
using namespace std;
const int N = 100000+10;
int Q[N];
int L[N];
int cnt1[10000+10],cnt2[10000+10];
long long sum1[10000+10],sum2[10000+10];
int main(void) {
int n,m;
cin>>n>>m;
assert(m<=10000);
for(int i=1;i<=n;++i)
scanf("%d",Q+i);
for(int i=1;i<=n;++i)
{
scanf("%d",L+i);
if(i&1) cnt1[L[i]]++,sum1[L[i]]+=i;
else cnt2[L[i]]++,sum2[L[i]]+=i;
}
long long ans = 0;
for(int i=1;i<=n;++i) {
if(i&1) {
int m = cnt1[L[i]];
if(m<=1) continue; //沒人和它配對
ans = (ans+1ll*i*Q[i]*(m-1)+1ll*Q[i]*(sum1[L[i]]-i))%10007;
} else {
int m = cnt2[L[i]];
if(m<=1) continue;
ans = (ans+1ll*i*Q[i]*(m-1)+1ll*Q[i]*(sum2[L[i]]-i))%10007;
}
}
cout<<ans<<endl;
return 0;
}
23. 丟手絹
分析:
說白了就是一個有向圖,每個節點只有一個出度。找出這個圖中長度最小的環,輸出這個長度。(題目說保證有答案,就是保證有環)
注意,這個圖可能是不連通的,可能有很多子圖,所以我們要從每個點出發都dfs一次才能確保能找到最優答案。
這個題每個結點出度爲1,我們甚至不用dfs,直接循環就好了,但是dfs更好寫一點,代碼更簡潔。我們可以從每個點出發dfs,沿着箭頭走。然後標記每個結點在這一趟中出現的次序。如果在這一趟中,某個結點出現了兩次,那麼就說明沿着環走到了之前走過的結點,那麼環的大小就爲這個結點先後兩次的次序只差,我們就結束這趟dfs, 因爲再搜下去也是一樣的路線。
我們要注意,前幾趟dfs中走過的結點,在後面是不需要走的,因爲之前走過的環,從外部進入不會得到更小的環。所以,我們每個結點走一次即可。複雜度O(n)。
代碼:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int to[N],vis[N],cntt[N];
int ans;
void dfs(int x,int id,int cnt) {//當前結點,在這一輪中的標號,輪數
if(vis[x]) {
if(cntt[x]<cnt) return;
ans = min(ans,id-vis[x]);
return;
}
vis[x] = id;
cntt[x] = cnt;
dfs(to[x],id+1,cnt);
}
int main(void) {
ans = 1e7;
int n;cin>>n;
for(int i=1;i<=n;++i)
scanf("%d",to+i);
for(int i=1;i<=n;++i) {
if(!vis[i]) dfs(i,1,i);
}
cout<<ans<<endl;
return 0;
}
24. 有趣的最大池化
樣例輸入1
5
31 24 21 14 22
1
樣例輸出1
31 24 21 14 22
樣例輸入2
5
18 14 31 1 26
2
樣例輸出2
18 31 31 26
樣例輸入3
16
61 53 2 13 51 30 48 44 58 46 36 8 2 8 34 10
7
樣例輸出3
61 53 58 58 58 58 58 58 58 46
分析:
經典單調隊列問題,解決滑動窗口的最大值。我們可以維護一個單調的隊列,隊列從隊首到隊尾(從左到右)存儲的元素單調遞減。隊首元素即爲該區間的最大元素。
我們從1到n遍歷原數列中的數,到a[i],我們先去尾, 如果隊尾(最右邊)的元素比a[i]小的話,它對後面所有長度爲len的區間的最大值都是沒有意義的。去尾之後我們將a[i]從隊尾加入到隊列之中。然後我們刪頭,如果隊首代表的元素的位置比隊尾的還小len-1, 那麼說明已經在窗口左區間的左邊了,沒有意義,我們要去掉這個沒有意義的元素。然後此時的隊首就是以i爲區間右端點,長度爲len的區間(窗口)中最大的元素了。
隊列中只需要存數組元素的下表即可。手動模擬隊列比STL::queue要快一點。
代碼:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5;
int a[N],S[N];//單調隊列,記錄id,隊尾維護最大值,
int main(void) {
int n,len;
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",a+i);
scanf("%d",&len);
int pa = 1,pb = 0;
for(int i=1;i<=n;++i) {
while(pb>=pa&&a[S[pb]]<=a[i]) --pb;//
S[++pb] = i;
while(i-S[pa]+1>len) ++pa;
if(i>=len) printf("%d ",a[S[pa]]);
}
return 0;
}
2020/4/10 23:28
xzc