省流版
- A. 模擬即可
- B. 貪心,有\(k\)個就吃,模擬即可
- C. 維護已經有棋子的格子,有多個棋子往右推,代價等差數列求和,模擬即可
- D. 注意到植物高度=
當前天
-種植天
,維護植物的種植天然後二分找對應高度的植物即可 - E. 考慮最終答案每一個數位的值,然後處理進位即可
- F. 單調棧處理建築\(r\)能看的建築數量,然後求 \([l+1,r]\)最大高度,二分找到遮蔽單調棧的樓數量即可
- G. 維護輪廓線狀態\(dp\),僅從有效狀態往後\(dp\)即可
A - Cyclic (abc379 A)
題目大意
給定三個數字,將其進行兩次迴圈移位並輸出。
解題思路
可以用rotate
函式,將首字母移動到末尾。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
string a;
cin >> a;
rotate(a.begin(), a.begin() + 1, a.end());
cout << a << ' ';
rotate(a.begin(), a.begin() + 1, a.end());
cout << a << '\n';
return 0;
}
B - Strawberries (abc379 B)
題目大意
\(n\)個牙齒,如果有連續 \(k\)個牙齒是健康的,則可以吃一個草莓,然後這 \(k\)個牙齒變壞了。
問最多能吃多少個草莓。
解題思路
從左往右依次考慮每個牙齒,很顯然,一旦有連續\(k\)個健康的就吃一個草莓,該決策一定不劣。
模擬該策略即可。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, k;
string s;
cin >> n >> k >> s;
int ans = 0;
string good = string(k, 'O');
for (int i = 0; i < n; i++) {
if (s.substr(i, k) == good) {
++ans;
fill(s.begin() + i, s.begin() + i + k, 'X');
}
}
cout << ans << '\n';
return 0;
}
C - Sowing Stones (abc379 C)
題目大意
\(n\)個格子,初始有 \(m\)個格子有棋子數 \(a_i\)。
現可進行操作,如果第 \(i\)個格子有棋子,可以將其一個棋子移動到第 \(i+1\)個格子。
問最少進行的運算元,使得每個格子都恰好有一個棋子。
解題思路
首先看棋子總數是不是\(n\),不是則做不到。
然後從左到右考慮每個格子,如果其棋子數 \(>1\),則將多餘的部分往右推。
記\(cur\)為從左數第一個還沒棋子的格子(這個還沒棋子的格子
,不考慮該格子原先已有棋子的情況,僅考慮上述向右推的操作的棋子是否到達了該格子)。
對於該格子有 \(a_i\)個棋子,就可以把這些推到\([cur, cur + a_i - 1]\)這些格子,使得每個格子都恰好有一個棋子,其代價是一個等差數列的求和。然後 \(cur = cur + a_i\),考慮下一個格子的棋子往右推即可。
注意 \(a_i \leq 2e9\),等差數列時的 \(l+r\)可能會超 \(int\)範圍。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
vector<int> x(m), a(m);
for (auto& i : x)
cin >> i;
for (auto& i : a)
cin >> i;
if (accumulate(a.begin(), a.end(), 0ll) != n) {
cout << -1 << '\n';
} else {
vector<int> id(m);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(), [&](int i, int j) { return x[i] < x[j]; });
LL ans = 0;
int cur = 1;
auto calc = [&](int l, int r, int x) {
int cnt = r - l + 1;
return (0ll + l + r) * cnt / 2 - 1ll * x * cnt;
};
bool ok = true;
for (auto& i : id) {
if (cur < x[i]) {
ok = false;
break;
}
ans += calc(cur, cur + a[i] - 1, x[i]);
cur += a[i];
}
if (!ok) {
ans = -1;
}
cout << ans << '\n';
}
return 0;
}
D - Home Garden (abc379 D)
題目大意
問題陳述
高橋有 \(10^{100}\) 個花盆。最初,他沒有種植任何植物。
依次處理 \(Q\) 個操作,分三種
1
:準備一個空花盆並放入一株植物。此時植物的高度是 \(0\) 。2 T
:等待 \(T\) 天,現有植物的高度會增加 \(T\) 。3 H
:收穫所有高度至少達到 \(H\) 的植株,並輸出收穫植株的數量。收穫的植物會從花盆中移出。
解題思路
植物高度為當前天
-種植天
,因此我們只需維護每個植物的種植天
,對於操作三,假設當天是第\(x\)天 ,那隻需把所有種植天
\(\leq x - H\)的植物全收割即可。
因為植物的 種植天
是不斷遞增的,因此就用一個陣列維護一個遞增的種植天,然後再用一個變數\(la\)維護,已經被收割植物 的種植天
的最大值,每次收割,二分找到\(x - H\)的位置,其與 \(la\)的差值就是本次收割的植株數量。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int q;
cin >> q;
vector<LL> pot;
int l = 0;
LL day = 0;
while (q--) {
int op;
cin >> op;
if (op == 1) {
pot.push_back(day);
} else if (op == 2) {
int t;
cin >> t;
day += t;
} else if (op == 3) {
int h;
cin >> h;
int nxt =
upper_bound(pot.begin() + l, pot.end(), day - h) - pot.begin();
cout << nxt - l << '\n';
l = nxt;
}
}
return 0;
}
E - Sum of All Substrings (abc379 E)
題目大意
給定一個長達\(1e5\)的數字字串\(s\)。定義 \(f(i,j) = s[i..j]\) 。即子串的數字。
求\(\sum_{i=1}^{n}\sum_{j=1}^{n} f(i,j)\)。
解題思路
\(f(i,j) = s[i..j] = s[j] + s[j - 1] \times 10 + s[j - 2] + ...\)。
這樣我們可以不考慮每個 \(f(i,j)\),而是考慮每個 \(s[j]\)在答案的貢獻,但是因為數很大,這樣考慮得寫高精度。
怎麼辦呢?我們還可以考慮最終答案的每一位的數字是多少,即因子\(10,10^2,10^3\)的係數分別是多少。
考慮個簡單的例子,即
這樣我們就知道個位的數是\(S_1+2S_2+3S_3\),處理好進位後,再考慮十位的數是多少。
更一般的,即\(A_i=\sum_{j=1}^i j\times S_j\),最終的答案就是\(\sum_{i=1}^N 10^{N-i}A_i\)。再從低位依次考慮進位的情況即可。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
string s;
cin >> n >> s;
LL sum = 0;
for (int i = 0; i < n; ++i)
sum += (i + 1) * (s[i] - '0');
vector<LL> ans;
ans.push_back(0);
for (int i = n - 1; i >= 0; --i) {
ans.back() += sum % 10;
int jin = ans.back() / 10;
ans.back() %= 10;
ans.push_back(sum / 10 + jin);
sum -= (i + 1) * (s[i] - '0');
}
while (ans.back() >= 10) {
int jin = ans.back() / 10;
ans.back() %= 10;
ans.push_back(jin);
}
while (ans.back() == 0)
ans.pop_back();
reverse(ans.begin(), ans.end());
for (auto& i : ans)
cout << i;
cout << '\n';
return 0;
}
F - Buildings 2 (abc379 F)
題目大意
給定\(n\)個建築的高度\(h_i\),回答 \(q\)個詢問。
每個詢問給定 \(l,r\),問從 \(l,l+1,...,r-1,r\)建築往右看,都能看到的建築的數量。
如果建築\(i\)能看到建築 \(j\),則不存在 \(i < k < j\),滿足 \(h_k > h_j\)。
解題思路
如果單問某個建築往右看,能看到的建築物數量,其實就是一個從右往左的單調棧——高的建築會遮蔽矮的建築,從而矮的建築會出棧,使得棧是一個從棧頂到棧低是一個遞增的情況。
詢問可以離線,因此我們按照\(r\)從大到小的順序考慮每個詢問,當前單調棧的結果是從\(r\)往右看能看到的建築物高度,然後考慮\([l,..r-1]\)它們的有哪些看不到。
注意到其建築看到的條件:
- 如果建築\(i\)能看到建築 \(j\),則不存在 \(i < k < j\),滿足 \(h_k > h_j\)。
\(j\)不變, 序號\(i\)越小,其看到的條件就越苛刻,即 \(i\)能看到的建築, \([i+1,j-1]\)一定能看到。反之不能看到的話,卻不一定。
但我們要找 \([l,r]\)都能看到的建築,其實就是 \(l\)能能看到的建築數量,按照單調棧的做法,我們即找到[l+1,r]中最高的建築\(H\),它會遮蔽單調棧裡高度小於 \(H\)的建築。
無修改的區間找最大值可以用 \(ST\)表,然後找出遮蔽建築物的數量,因為單調棧是單調的,因此可以二分找到遮蔽建築的分界線,進而得出可以看到的建築物的數量。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
template <typename T, class F = function<T(const T&, const T&)>>
class SparseTable {
public:
int n;
vector<vector<T>> mat;
F func;
SparseTable(const vector<T>& a, const F& f) : func(f) {
n = static_cast<int>(a.size());
int max_log = 32 - __builtin_clz(n);
mat.resize(max_log);
mat[0] = a;
for (int j = 1; j < max_log; j++) {
mat[j].resize(n - (1 << j) + 1);
for (int i = 0; i <= n - (1 << j); i++) {
mat[j][i] = func(mat[j - 1][i], mat[j - 1][i + (1 << (j - 1))]);
}
}
}
T get(int from, int to) const { // [from, to]
assert(0 <= from && from <= to && to <= n - 1);
int lg = 32 - __builtin_clz(to - from + 1) - 1;
return func(mat[lg][from], mat[lg][to - (1 << lg) + 1]);
}
};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, q;
cin >> n >> q;
vector<int> h(n);
for (auto& i : h)
cin >> i;
SparseTable<int> st(h, [&](int i, int j) { return max(i, j); });
vector<array<int, 2>> query(q);
for (auto& i : query)
cin >> i[0] >> i[1];
vector<int> id(q);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(),
[&](int a, int b) { return query[a][1] > query[b][1]; });
vector<int> ans(q);
vector<int> stack;
int cur = n - 1;
for (auto i : id) {
auto [l, r] = query[i];
--l, --r;
while (cur > r) {
int hei = h[cur];
while (!stack.empty() && hei >= stack.back()) {
stack.pop_back();
}
stack.push_back(hei);
--cur;
}
int hei = st.get(l + 1, r);
auto pos =
upper_bound(stack.begin(), stack.end(), hei, greater<int>()) -
stack.begin();
ans[i] = pos;
}
for (auto i : ans)
cout << i << '\n';
return 0;
}
G - Count Grid 3-coloring (abc379 G)
題目大意
給定\(h \times w\)的格子,每個格子寫著 ?123
中的一種。
現將所有的?
替換成123
中的一個。
問有多少替換方法,使得相鄰格子的數字不相同。
解題思路
從上到下,從左到右依次考慮每行每列的格子\((x,y)\),看其?
能否替換成123
,取決於上一行\((x-1,y)\)的數字和上一列\((x,y-1)\)的數字是否與該格子相同。
因此我們的狀態得保留這兩處格子的資訊,但是僅僅保留這兩處的話,當前狀態變成下一列時,無法繼承之前的狀態。
怎樣才能繼承之前的狀態呢?那就需要保留一個輪廓的狀態(即已考慮的格子的邊界)。如下圖藍色格子所示。此即為輪廓線\(dp\)。
即 \(dp[i][j][s]\)表示從上到下,從左到右考慮到格子 \((i,j)\),外圍一圈的數字狀態為 \(s\)(這裡是一個 \(3^w\)的壓縮狀態)。
轉移即考慮當前格子的數字是什麼,是否合法即可。由於 \(hw \leq 200\),因此 \(\min(h,w) \leq 14\),保持 \(h \leq w\),則時間複雜度為 \(O(hw3^w)\),有 \(1e9\)。
但考慮到\(s\)的有效 狀態數沒有\(3^w\)(相鄰相同的數字是非法狀態),而是 \(O(2^w)\)(因為不能和前一列相同,因此該列的數字只有兩種,然後還有一些組合數的係數),因此僅從有效狀態轉移,時間複雜度為 \(O(hw2^w)\)。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mo = 998244353;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int h, w;
cin >> h >> w;
vector<string> s(h);
for (auto& i : s)
cin >> i;
if (h < w) {
vector<string> t(w, string(h, ' '));
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
t[j][i] = s[i][j];
}
}
swap(h, w);
swap(s, t);
}
vector<int> base(w, 1);
for (int i = 1; i < w; ++i) {
base[i] = base[i - 1] * 3;
}
auto get_bit = [&](int x, int y) { return x / base[y] % 3; };
auto tr = [&](int cur, int pos, int v) {
int ret = cur;
ret -= get_bit(cur, pos) * base[pos];
ret += v * base[pos];
return ret;
};
auto ok = [&](int x, int y, int cur, int v) {
if (y != 0 && get_bit(cur, y - 1) == v)
return false;
if (x != 0 && get_bit(cur, y) == v)
return false;
return true;
};
int up = 1;
for (int i = 0; i < w; i++) {
up *= 3;
}
map<int, int> dp;
dp[0] = 1;
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
map<int, int> ndp;
for (auto& [k, v] : dp) {
if (s[i][j] != '?') {
if (ok(i, j, k, s[i][j] - '1')) {
auto nxt = tr(k, j, s[i][j] - '1');
ndp[nxt] += v;
if (ndp[nxt] >= mo)
ndp[nxt] -= mo;
}
} else {
for (int l = 0; l <= 2; ++l) {
if (ok(i, j, k, l)) {
auto nxt = tr(k, j, l);
ndp[nxt] += v;
if (ndp[nxt] >= mo)
ndp[nxt] -= mo;
}
}
}
}
dp.swap(ndp);
}
}
int ans = 0;
for (auto& [k, v] : dp) {
ans += v;
if (ans >= mo)
ans -= mo;
}
cout << ans << '\n';
return 0;
}