本文將研討 Codeforces 777(Codeforces Round 401 (Div. 2))中的題目 A-E。
題目連線
A B C D E
題目分析
A
難度:普及−
題面翻譯:
給你三張牌:\(0\),\(1\),\(2\)。
最初選一張,然後依次進行 \(n\) 次交換,交換規則為:中間一張和左邊的一張,中間一張和右邊一張,中間一張和左邊一張……
最後問選的那張在哪個位置。
演算法標籤:模擬、列舉、構造、數學
題解:
注意到每 \(6\) 次操作為一個週期,在經過一週期後序列被還原成操作前的形態。因此打個表然後直接做就做完了。
B
難度:普及−
題面翻譯:
有兩個人 S 和 M,他們每人有一段長度為 \(N\) 的數字,兩個人在每一輪遊戲中都可以按順序拿出一個數字,誰的數字小誰就接受一次懲罰。若相等兩者都沒有懲罰。另外,M 可以重新安排自己數字的順序,問 M 的最少被懲罰次數和 S 的最多被懲罰次數是多少。
演算法標籤:博弈論、資料結構、貪心、排序
題解:
先排個序。
參考這個博弈論的現實應用,可以得出以下策略:
- 若此輪能贏,則使用最小的能贏下來的牌。
- 若此輪不能贏,則出當前最小的牌。
證明略。
設 S 的牌為 \(s_i\),M 的牌為 \(m_i\)。顯然,對於第一問,只要 \(s_i \ge m_i\) 就能出 \(m_i\) 這張牌。
而對於第二問,出牌條件改為 \(s_i>m_i\) 即可。
C
難度:普及+/提高
題面翻譯:
給出一個 \(n\times m\) 的矩陣。對於第 \(j\) 列,如果滿足 \(\forall i \in [1,n-1],a_{i,j} \leq a_{i+1,j}\),則稱這一列是不下降的。
\(k\) 次詢問,問如果只保留矩陣的第 \(L\)~\(R\) 行,矩陣中是否存在不下降的一列。
演算法標籤:二分、資料結構、動態規劃、貪心、雙指標
題解:
注意到本題和最長不下降子串有關。用 \(b_{i,j}\) 表示以 \(a_{i, j}\) 結尾的最長不下降子串起始位置的下標(注意不是長度),然後預處理出每一行最小的 \(b\) 即可在 \(O(1)\) 內查詢。
#include <bits/stdc++.h>
using namespace std;
int n, m, k, a[200010], b[200010], c[200010];
int coordinate(int x, int y) {
return x * m + y;
}
int main() {
memset(c, 0x3f, sizeof(c));
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%d", &a[coordinate(i, j)]);
}
}
for (int j = 1; j <= m; j++) {
for (int i = 1; i <= n; i++) {
b[coordinate(i, j)] = ((a[coordinate(i, j)] < a[coordinate(i - 1, j)]) ? i : b[coordinate(i - 1, j)]);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
c[i] = min(c[i], b[coordinate(i, j)]);
}
}
scanf("%d", &k);
while (k--) {
int l, r;
scanf("%d%d", &l, &r);
if (c[r] <= l) puts("Yes");
else puts("No");
}
return 0;
}
D
難度:普及+/提高
題面翻譯:
給出 \(n\) 個開頭是
#
的字串,你需要透過操作使這些字串按字典序從小到大排列。
每次操作,你可以把某一個字串的任意字尾去掉(當然你甚至可以去掉整個字串),要求你去掉的字元數量最少。
輸出你操作完的這 \(n\) 個字串。
演算法標籤:二分、貪心、字串
題解:
不難發現,本題和排序沒有任何關係。
注意到去除字尾會使字串的字典序變小,因此 \(s_n\) 不需要變化。而對於 \(s_i(1\le i\le n-1)\),可以貪心的操作,使其剛好小於等於 \(s_{i+1}\)。因此從後往前操作即可。
#include <bits/stdc++.h>
using namespace std;
int n;
string s[500010];
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> s[i];
for (int i = n - 1; i >= 1; i--) {
int t = -1;
for (int j = 0; j < s[i].size(); j++) {
if (j == s[i + 1].size()) {
t = j;
break;
}
if (s[i][j] < s[i + 1][j]) break;
if (s[i][j] > s[i + 1][j]) {
t = j;
break;
}
}
if (t != -1) s[i].erase(t);
}
for (int i = 1; i <= n; i++) cout << s[i] << "\n";
return 0;
}
E
難度:提高+/省選−
題面翻譯:
給出 \(n\) 個環形零件的厚度、內徑、外徑,要求按以下條件裝配若干圓盤後,形成的塔最高。
- 圓盤應平放。也就是說,對於每個安放的零件,側檢視應當為長方形,而俯檢視應為圓環。
- 圓盤應疊放,而下方的圓盤外徑應大於等於上方圓盤的外徑。
- 上方圓盤的外徑應嚴格大於下方圓盤的內徑(否則上方圓盤會因重力作用掉到下方圓盤的孔裡)。
演算法標籤:列舉、排序
題解:
這道題和最長上升子序列有點像。先按照 \(b_i\) 降序對圓環排序,方便轉移。
設 \(\text{dp}_i\) 為以 \(i\) 為頂的最大塔高,則有轉移:
。於是你 T 了。
因此考慮引入堆最佳化。將先前的狀態壓入大根堆中(按 \(h\) 排序),直接彈出 \(a \ge b_i\) 的棧頂(因為此時堆頂不合法,接下來堆頂一定更不合法),直到合法,然後更新答案即可。
時間複雜度 \(O(n\text{log}{n})\),可以透過。
#include <bits/stdc++.h>
using namespace std;
#define int long long
struct dat {
int a, b, h;
} ring[100010];
int n, dp[100010];
bool cmp(dat a, dat b) {
if (a.b != b.b) return a.b > b.b;
return a.a > b.a;
}
bool operator < (dat x, dat y) { return x.h < y.h; }
priority_queue<dat> q;
signed main() {
scanf("%lld", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld%lld%lld", &ring[i].a, &ring[i].b, &ring[i].h);
}
sort(ring + 1, ring + n + 1, cmp);
q.push({0, 0, 0});
long long ans = 0;
for (int i = 1; i <= n; i++) {
while (q.top().a >= ring[i].b) q.pop();
dp[i] = q.top().h + ring[i].h;
q.push({ring[i].a, ring[i].b, q.top().h + ring[i].h});
ans = max(ans, dp[i]);
}
printf("%lld\n", ans);
return 0;
}
總結
這套題在 Codeforces 中屬於較簡單水平,有希望考場 AK(只是有希望)。
參考資料:
[1] Sima Qian.The Biography of Sun Wu & Wu Qi[A]Records of the Grand Historian[C].Chang'an:Yang Yun,91BCE:1-2