12/24
切分「資料」與「邏輯」
有三個杯子杯底朝上排成一橫排(左、中、右),左杯放一顆球,給一連串交換指令後,求球位在哪個杯子?
bool left = true;
bool mid = false, right = false;
for (i=0; i<cmd.size(); i++)
{
if (cmd[i] == 'A')
{
swap(left, mid);
}
else if (cmd[i] == 'B')
{
swap(mid, right);
}
else
{
swap(left, right);
}
}試想杯子變成 10 個的話,如何修改?
const int CUP_N = 3;
const int TARGET[CUP_N][2] = {{0, 1}, {1, 2}, {0, 2}};
bool cup[CUP_N] = {true};
for (i=0; i<cmd.size(); i++)
{
int t = cmd[i] - 'A';
swap(cup[ TARGET[t][0] ], cup[ TARGET[t][1] ]);
}給日期,輸出星座
這類問題還有個特色是極難 debug
月日太麻煩,全轉成天數方便比對
月份天數不同,老實轉不如隨便轉
X 月 Y 日 → X * 100 + Y
寫 12 個 if 轉換月份英文 → 數字?
const string MONTH[] = {"", "Jan", "Feb", "Mar", "Apr",
"May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec"};
int str_to_month(const string& s)
{
for (int i=1; i<=12; i++)
{
if (s == MONTH[i])
{
return i;
}
}
return -1;
}int day;
string mon_str;
cin >> day >> mon_str;
int res = str_to_month(mon_str) * 100 + day;對十二星座各寫 if?
const int DAY[] = {120, 219, 320, 420,
520, 621, 722, 822,
921, 1022, 1122, 1221, 9999};
const string NAM[] = {"Capricorn", "Aquarius", "Pisces", "Aries",
"Taurus", "Gemini", "Cancer", "Leo",
"Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn"};int day;
string mon_str;
cin >> day >> mon_str;
int res = str_to_month(mon_str) * 100 + day;
int ans;
for (ans=0; res>DAY[ans]; ans++);
cout << ans << "\n";就是如此優雅簡潔
先想「有什麼表方便」再想「如何去建」
給一棋盤盤面,問是否恰有 9 隻互不攻擊的西洋棋騎士?
bool gg = false;
int knight_num = 0;
// 窮舉所有座標
for (i=0; i<5; i++)
{
for (j=0; j<5; j++)
{
// 如果是騎士
if (board[i][j] == 'k')
{
knight_num++;
// 檢查攻擊範圍
if (i-2 >= 0 && j-1 >= 0 && board[i-2][j-1] == 'k')
{
gg = true;
}
if (i-2 >= 0 && j+1 < 5 && board[i-2][j+1] == 'k')
{
gg = true;
}
if (i-1 >= 0 && j-2 >= 0 && board[i-1][j-2] == 'k')
{
gg = true;
}
if (i-1 >= 0 && j+2 < 5 && board[i-1][j+2] == 'k')
{
gg = true;
}
if (i+1 < 5 && j-2 >= 0 && board[i+1][j-2] == 'k')
{
gg = true;
}
if (i+1 < 5 && j+2 < 5 && board[i+1][j+2] == 'k')
{
gg = true;
}
if (i+2 < 5 && j-1 >= 0 && board[i+2][j-1] == 'k')
{
gg = true;
}
if (i+2 < 5 && j+1 < 5 && board[i+2][j+1] == 'k')
{
gg = true;
}
}
}
}
if (knight_num != 9)
{
gg = true;
}容易錯,不容易發現,不好救
不規則的東西,扔進陣列就會乖了
const int DX[] = {2, 2, 1, 1, -1, -1, -2, -2};
const int DY[] = {1, -1, 2, -2, 2, -2, 1, -1};
bool in_range(int x, int y)
{
return x >= 0 && x < 5 && y >= 0 && y < 5;
}bool gg = false;
int knight_num = 0;
// 窮舉所有座標
for (i=0; i<5; i++)
{
for (j=0; j<5; j++)
{
// 如果是騎士
if (board[i][j] == 'k')
{
knight_num++;
// 檢查攻擊範圍
for (k=0; k<8; k++)
{
int tx = i + DX[k];
int ty = j + DY[k];
if (in_range(tx, ty) && board[tx][ty] == 'k')
{
gg = true;
}
}
}
}
}
if (knight_num != 9)
{
gg = true;
}用來對付像「A 和 B 的關係」用
和 N 朋友玩 M 回猜拳,每回合出一種拳,和所有朋友個別判定勝負來計算得分。求按題目給定的出拳順序的得分、以及預先知道朋友出拳時可能的最高得分。
每次猜贏得 2 分,平手得 1 分,輸掉得 0 分。
| 主角\朋友 | S | P | R |
|---|---|---|---|
| S | 1 | 2 | 0 |
| P | 0 | 1 | 2 |
| R | 2 | 0 | 1 |
const int WIN = 2;
const int TIE = 1;
const int LOSE = 0;
const int SCORE[3][3] = {
{TIE, WIN, LOSE}, // S -> SPR
{LOSE, TIE, WIN}, // P -> SPR
{WIN, LOSE, TIE}, // R -> SPR
};手動建會有點痛扣時用
給一字串為鍵盤輸入時,全往右按偏 1 格的結果,求它原本想輸入什麼?
for (i=0; i<s.size(); i++)
{
s = tbl[ s[i] ];
}超級快樂
tbl['W'] = 'Q';
tbl['E'] = 'W';
tbl['R'] = 'E';
tbl['T'] = 'R';
// ...... 將近 50 組// 滑過鍵盤四趟即可,不用 10 秒
const string KEY = "`1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./";
char tbl[128];for (i=1; i<KEY.size(); i++)
{
tbl[ KEY[i] ] = KEY[i-1];
}
tbl[' '] = ' ';如果二段也很痛扣,何不讓程式代勞
按下表計算列印墨水費
char c;
int d;
while (cin >> c >> d)
{
cout << "tbl['" << c << "'] = " << d << ";\n";
}你可以開始嘲笑旁邊寫幾十個 if 的勤勞朋友了
記得微調特殊字元
直接求解困難時
那你有試過窮舉嗎?
已知 X1 可能範圍為 0 到 n
窮舉 X1 所有可能,每種得到 X2 = n - X1
代入求收益 Y1+Y2,取最大者
找出所有可能情形,每一組代回去比對
給 12 硬幣,已知有 1 為假,和真硬幣重量不同,但可能較輕、可能較重;
給多次秤重時天平兩邊硬幣與結果,求假硬幣為何、以及是較真硬幣輕或重。
人類習慣邏輯推論,方向不對還能想新的
電腦不行,一開始的招式不完整就是錯
共計 12 x 2 = 24 種
設 A 為輕,則 W[A] = 1、W[其它] = 2
檢查所有秤重結果是否矛盾
左右兩側各自查表相加,檢查大小,簡單吧?
如果所有情況會是數對等多參數的東東
求區間 [l, r] 使區間內所有元素乘起來最大
負數的存在使得區間並非越長越好,怎辦?
N 最大 18,此時區間數 (18 * 17) / 2 = 153
long long best = -1e18;
for (i=0; i<n; i++)
{
long long t = 1;
for (j=i; j<n; j++)
{
t *= ary[i];
best = max(best, t);
}
}練打字?練短碼?
那怎辦?
一次 AC 最快
哪一步能偷時間?
如果你理解錯,寫對時就是不符題意
那你再強都沒用
不先規劃,邊寫邊想容易前後矛盾對不起來
能節省的有限,大多有害
時間最沒上限、最難壓但也最沒副作用
流程先拆步驟,定義規範、個別測試
完成數個小任務,比單一大任務簡單
決定每步輸入什麼、計算出什麼、代表什麼意義
定義輸出輸入的規格,例如存在哪些變數、index 開頭結尾…等
每步各自測試,bug 相對好找、究責時相對容易
測資必須以「證明這程式是錯的」出發,出到無能為力為止
可以想像這份是你討厭的人寫的,有助於找出盲點或破綻
其它像是身心狀態的管理與調節、資源分配…
其實這異常重要
不論是否依難度排序
以「一次沒過,這題就 0 分」的心態面對
並且計時,習慣時間壓力,了解自己做什麼需要多久,和實際費時比對、修正誤差
從 CP 值最高的做,估時越準越有幫助
一定時間內進展過小,就先想別題或喝個水等
跳脫目前思考後再重新來過
嘗試說明有助整理和找出缺漏
出 bug 時說明想法有助找出問題和破綻
不想給別人看 or 禁止交談時,打字也行
測試佔非常多時間,建議複製貼上
題目純紙本時,可打在 IDE 上,善用全選
必要時寫成多重輸入來應對多測資
(建議一定程度以上後)
練習特定題型題單,會有太多事前資訊
難以訓練題型判讀與面對未知時如何找線索
核心在管理狀態的變化
例如現在回合、輪到玩家幾、目前階段…
例如勝敗、死活、站立坐下、高興難過、…
一對一映射至整數,宣告常數增加可讀性
同回合的行動無法「同時進行」
可記錄變動量延時套用