前言
作爲一名退役 OIer,藉着此比賽來複健。
大部分題目都是口胡(沒啥好寫的),spj題手寫了代碼,A了。
難度不算特別高,但是賽場上拿到高分略有壓力(退役了可以瞎bb)。
個人認爲應該要拿到 300+ 的分數,但是感覺 AH 成績普遍不好?
spj 題(尤其是構造題)一定要多重視啊,我當年沒趕上這個好時代啊~
種花
洛谷標籤爲綠,pass
簽到題,如果出事了建議去 pj 組歷練。
懶得討論。
喵了個喵
貌似是照應《羊了個羊》。是一道很有意思的 cf 類型題,大概會是 div1 中決定 rank 在 100 內還是外的分界線的難度。
此題在官方數據範圍的指引下難度降了很多。首先 \(k=2n-2\) 十分容易,只需要維持一個空棧即可,其他每個棧至多兩張卡牌。上層卡牌利用方法 1,下層卡牌利用方法 2 即可。
然後思考如何解決 \(k=2n-1\)。上面的策略在遇到連續 \(2n-1\) 個不同卡牌時,就不能維持一個空棧。首先在茫茫之中要明確一個原則:所有的棧中至多 \(2n-1\) 個卡牌,更進一步,不存在兩個相同卡牌,否則將會把問題帶向深淵,愈加複雜。
當遇到這種不可調和的情況時,仔細思考發現可以分 3 類情況討論。首先先定義:棧中放入卡牌 \(X\) 後,所有棧有 \(2n-1\) 張不同卡牌,放 \(X\) 前有 \(2n-2\) 張卡牌,也就是說有 \(n-1\) 個棧,每個棧有兩張卡牌。下層的卡牌集合記爲 \(\{A\}\),上層爲 \(\{B\}\)。
牌 \(X\) 之後,必然還會遇到一次 \(X\)。在 \(X\) 之前,我們可能會遇到 \(A\) 或 \(B\)。
第一種情況:在兩次 \(X\) 之間全是 \(B\)。那麼 \(X\) 放空棧即可,對後續操作不會有影響。
假如在兩次 \(X\) 之間含有 \(A\),且第一個 \(A\) 在棧 \(i\) 中,棧 \(i\) 中還有一個上層卡牌 \(B_0\),第二種情況是:\(B_0\) 不在 \(X\) 到 \(A\) 之間,那麼就把 \(X\) 放到第 \(i\) 個棧頂,然後後面正常操作,直到遇到 \(A\),此時空棧仍然是空棧,利用其消去 \(A\),那麼仍然存在空棧並且其它棧元素均不超過 2,可以繼續。
那麼當 \(B_0\) 出現在 \(X\) 和 \(A\) 之間,採用上面的方法會導致棧中已有的 \(B_0\) 無法消去,於是爲第三種情況。那麼就把 \(X\) 放到空棧裏,當操作到 \(B_0\) 時,\(i\) 棧僅剩一個元素 \(A\),如果之後再次遇到 \(B_0\),就放到 \(X\) 頂上,這樣遇到了 \(A\) 以後,直接放在 \(i\) 棧即可消去 \(A\),此時 \(i\) 棧變成了空棧,並且其它棧元素均不超過 2。
複雜度 \(\mathcal O(n+m)\)。
#include <bits/stdc++.h>
const int N = 305, M = 2e6 + 5;
int T, n, m, k, a[N], b[N], c[N * 2], cur, s[M];
std::stack<int> s1, s2;
struct step { int x, y, z; };
std::vector<step> ans;
void opt_1(int x) {
ans.push_back({1, x, 0});
}
void opt_2(int x, int y) {
ans.push_back({2, x, y});
}
bool solve(int x) {
if (c[x]) {
if (c[x] < 0) {
int u = -c[x];
opt_1(u); b[u] = 0;
s2.push(u);
}
else {
int u = c[x];
opt_1(cur); opt_2(u, cur);
a[u] = b[u];
if (b[u]) {
c[b[u]] *= -1;
b[u] = 0;
s2.push(u);
}
else s1.push(u);
}
c[x] = 0;
}
else if (!s1.empty()) {
int u = s1.top(); s1.pop();
opt_1(u);
a[u] = x; c[x] = u; // 第一層 +
}
else if (!s2.empty()) {
int u = s2.top(); s2.pop();
opt_1(u);
b[u] = x; c[x] = -u; // 第二層 -
}
else return false;
return true;
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= m; i++)
scanf("%d", &s[i]);
while (!s1.empty()) s1.pop();
while (!s2.empty()) s2.pop();
ans.clear();
cur = n;
for (int i = 1; i < n; i++)
s1.push(i), s2.push(i);
for (int i = 1; i <= m; i++)
if (!solve(s[i])) {
int j = i + 1;
while (s[j] != s[i] && c[s[j]] < 0) j++;
if (s[j] == s[i]) {
opt_1(cur);
for (int k = i + 1; k < j; k++)
solve(s[k]);
opt_1(cur);
}
else {
int u = c[s[j]], p = 0;
for (int k = i + 1; k < j; k++)
if (c[s[k]] == -u) { p = k; break; }
if (p) {
opt_1(cur);
c[a[cur] = s[i]] = cur;
for (int k = i + 1; k < p; k++)
solve(s[k]);
opt_1(u);
b[u] = c[s[p]] = 0; s2.push(cur);
for (int k = p + 1; k < j; k++)
solve(s[k]);
opt_1(u);
a[u] = c[s[j]] = 0;
cur = u;
}
else {
opt_1(u);
for (int k = i + 1; k < j; k++)
solve(s[k]);
opt_1(cur); opt_2(u, cur);
c[s[j]] = 0;
c[a[u] = b[u]] = u;
c[b[u] = s[i]] = -u;
}
}
i = j;
}
printf("%lu\n", ans.size());
for (step o : ans)
if (o.x == 1) printf("1 %d\n", o.y);
else printf("2 %d %d\n", o.y, o.z);
}
return 0;
}
建造軍營
此題很裸,直接 縮點 + DP 計個數即可。沒什麼好說的。
比賽
看了眼數據範圍 + 時限,直接分塊。細節沒想好,想起來了就補。
好像可以 線段樹?不管了我 DS 很菜,想起來再補。