2018京東C++開發工程師實習線上筆試程式設計題參考題解
AC了第一題第三題(第一題被個低階錯誤浪費了一大堆時間。。。),第二題dp推錯了,一首涼涼獻給自己。
第一題
大致題意
題意是說,給你一個n(1<=n<=100000),要你找出1~n這麼多個數的最小公倍數。因為結果可能較大,對987654321取模。
思路
最小公倍數,那就很直接了,依次對每個數分解質因數,統計各個素因子的個數,最終這個素因子的個數取這n個數中的最大值。例如n取4,那麼素因子2的個數就要取2,不然就不是最小公倍數了。由於n取值較大,怕每個數都分解一次質因數會超時,就先打了個素數篩,再來分解。
(看別人貼出來的程式碼,似乎直接分解+暴力乘,不使用素數篩+快速冪也能過?)
然後本人犯了個很低階很低階的錯誤,把最終結果用素因子乘以次數,一直卡10%,直到最後半小時才發現,立馬趕一個快速冪取模出來才AC。。。記錄一下,打一下自己的臉,給自己提個醒。
參考程式碼
尷尬,筆試時太緊張,忘記把AC的程式碼儲存起來了。。。下面貼的是後來補的程式碼,由於題目沒開出來,無法測試正確性,不確定有沒有bug,大家參考一下思路即可。。
#include <iostream>
#include <array>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <string>
#include <cstdio>
#include <set>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <cctype>
using namespace std;
typedef long long LL;
const int MAXN = 1e5+5;
const LL MOD = 987654321;
array<LL, MAXN> prime;
array<bool, MAXN> isPrime;
array<LL, MAXN> num;
int pn;
void init() {
isPrime.fill(true);
isPrime[1] = false;
pn = 0;
for (int i=2; i<MAXN; ++i) {
if (isPrime[i]) {
prime[pn++] = i;
for (int j=i*2; j<MAXN; j+=i) {
isPrime[j] = false;
}
}
}
}
LL quickPow(LL a, LL b) {
LL ans = 1;
while (b) {
if (b & 1) ans = ans*a % MOD;
a = a*a % MOD;
b >>= 1;
}
return ans;
}
LL solve(int n) {
init();
num.fill(0);
LL ans = 1;
for (int i=2; i<=n; ++i) {
int tmp = i;
LL cnt = 0;
for (int j=0; j<pn&&tmp>1; ++j) {
while (tmp % prime[j] == 0) {
++cnt;
tmp /= prime[j];
}
num[prime[j]] = max(num[prime[j]], cnt);
if (isPrime[tmp]) {
num[tmp] = max(num[tmp], 1LL);
break;
}
}
}
for (int i=0; i<pn; ++i) {
if (num[prime[i]]) {
ans *= quickPow(prime[i], num[prime[i]]);
ans %= MOD;
}
}
return ans;
}
int main() {
std::ios::sync_with_stdio(false);
int n;
cin >> n;
cout << solve(n) << "\n";
return 0;
}
第二題
題意
給你一個字串(長度小於等於50),這個字串移除掉若干個字元(可以移除0個字元)後,若變成迴文串(不能是空串),就是一種可行的方案。如果兩種方法移除的字元按順序排列後不相同,就認為這是兩種不同的方案。問最終共有幾種方案。
思路
筆試的時候區間dp弄錯了,看牛客網討論區才明白。
設dp[i][j] 為區間[i, j]的方案數,則有
仔細琢磨一下遞推公式就可以理解,當左右端點不同時,就等於左邊這部分加上右邊這部分,然後因為中間交叉的部分在左右都算了一次,所以減掉一次;當左右端點相同時,就是在不相同的基礎上,加上中間部分(dp[i+1][j-1]),然後還有加上1,因為本身也算一個,化簡一下就變成上面的公式。(ps:markdown支援latex公式,寫出來的式子就是漂亮)
參考程式碼
#include <iostream>
#include <array>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <string>
#include <cstdio>
#include <set>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <cctype>
using namespace std;
typedef long long LL;
int main() {
std::ios::sync_with_stdio(false);
string str;
cin >> str;
LL dp[55][55];
memset(dp, 0, sizeof(dp));
int len = str.length();
for (int i=0; i<len; ++i)
dp[i][i] = 1;
for (int t=1; t<len; ++t) {
for (int i=0; i<len; ++i) {
int j = i+t;
if (j >= len) break;
dp[i][j] = dp[i][j-1] + dp[i+1][j] - dp[i+1][j-1];
if (str[i] == str[j]) {
++dp[i][j];
if (t > 1)
dp[i][j] += dp[i+1][j-1];
}
}
}
cout << dp[0][len-1] << "\n";
return 0;
}
第三題
(看到題目一大張中國象棋棋盤,讓我想起了許久沒下的象棋, 在象棋信念的支援下很快就AC)
題意
題目先介紹了象棋中的馬可以走8個方向,然後棋盤左下角為座標原點,向上為y軸正方向,向右為x軸正方向。輸入是一個整數k(k<=100000),以及一個座標x,y(0<=x<=8, 0<=y<=8),問,當馬一開始在座標原點時,走k步之後能夠停留在點(x, y)處的不同走法。最終結果對1000000007取模。
思路
這個資料範圍,還要取模,很明顯就是要dp了。馬在一個點上可以走8個方向,那麼也就意味著,對於棋盤上的每個點,最多有8個點能夠走一步到達。那麼本次該點的結果就是把這8個合法的點的值加起來取模就行了。迴圈k次即可得到答案。因為直接改變當前陣列的值的話,會導致後續的結果不正確,所以我用了一個臨時陣列tmp來儲存該次的結果,全部求出來之後在賦值回去。看了一下別人的程式碼,他們把兩個數組合起來,用dp[9][9][2]來運算,每次讓最後一維對1異或,就能保證兩次的結果不會互相干擾,這樣寫了之後程式碼會變得很簡潔,值得學習。這種方法我也寫出來,放在參考程式碼2那裡。
參考程式碼1
#include <iostream>
#include <array>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <string>
#include <cstdio>
#include <set>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <cctype>
using namespace std;
typedef long long LL;
const LL MOD = 1000000007;
const int MAXN = 9;
int dirx[] = {-2, -1, 1, 2, 2, 1, -1, -2};
int diry[] = {1, 2, 2, 1, -1, -2, -2, -1};
LL dp[MAXN][MAXN];
LL tmp[MAXN][MAXN];
bool isIn(int x, int y) {
return x >= 0 && x < MAXN && y >= 0 && y < MAXN;
}
LL solve(int k, int x, int y) {
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for (int i=0; i<k; ++i) {
for (int r=0; r<MAXN; ++r) {
for (int c=0; c<MAXN; ++c) {
tmp[r][c] = 0;
for (int p=0; p<8; ++p) {
int tx = r+dirx[p];
int ty = c+diry[p];
if (isIn(tx, ty))
tmp[r][c] = (tmp[r][c] + dp[tx][ty]) % MOD;
}
}
}
for (int r=0; r<MAXN; ++r) {
for (int c=0; c<MAXN; ++c) {
dp[r][c] = tmp[r][c];
}
}
}
return dp[x][y];
}
int main() {
std::ios::sync_with_stdio(false);
int k;
int x, y;
cin >> k >> x >> y;
cout << solve(k, x, y) << "\n";
return 0;
}
參考程式碼2
(這是簡潔一點的程式碼)
#include <iostream>
#include <array>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <string>
#include <cstdio>
#include <set>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <cctype>
using namespace std;
typedef long long LL;
const LL MOD = 1000000007;
const int MAXN = 9;
int dirx[] = {-2, -1, 1, 2, 2, 1, -1, -2};
int diry[] = {1, 2, 2, 1, -1, -2, -2, -1};
LL dp[MAXN][MAXN][2];
bool isIn(int x, int y) {
return x >= 0 && x < MAXN && y >= 0 && y < MAXN;
}
LL solve(int k, int x, int y) {
memset(dp, 0, sizeof(dp));
dp[0][0][0] = 1;
int cur = 0;
for (int i=0; i<k; ++i) {
cur ^= 1;
for (int r=0; r<MAXN; ++r) {
for (int c=0; c<MAXN; ++c) {
for (int p=0; p<8; ++p) {
int tx = r+dirx[p];
int ty = c+diry[p];
if (isIn(tx, ty))
dp[r][c][cur] = (dp[r][c][cur] + dp[tx][ty][cur^1]) % MOD;
}
}
}
}
return dp[x][y][cur];
}
int main() {
std::ios::sync_with_stdio(false);
int k;
int x, y;
cin >> k >> x >> y;
cout << solve(k, x, y) << "\n";
return 0;
}
總結
總結起來,還是自己菜啊,第一題bug找太久(因為測試的時候只測了小資料,每個素數的個數不超過2),第二題簡單的dp沒想清楚,也就第三題順了點。還需要多練,記錄一下,共勉。