省流版
- A. 統計計數即可
- B. 模擬更改即可
- C. 考慮每個壽司,找到滿足條件的位置最小值,即一個字首最小值
- D. 搜尋剪枝即可
- E. 期望題,根據期望定義寫出轉移式,從生成函式的角度求翻出 \(i\) 張稀有牌的機率
- F. 從深度最大的橫條開始,考慮下落時求解的每列深度然後更新,用線段樹維護即可
A - Daily Cookie (abc382 A)
題目大意
給定一個包含@.
的長度為\(n\)的字串,給定d
,表示將d
個@
變成.
,問.
的數量。
解題思路
統計@
的數量,然後減去d
,再用\(n\)減去這個值即可。
神奇的程式碼
n, d = map(int, input().split())
s = input().strip()
print(n - sum(1 for i in s if i == '@') + d)
B - Daily Cookie 2 (abc382 B)
題目大意
給定一個包含@.
的長度為\(n\)的字串,給定d
,表示將最右邊d
個@
變成.
,最終字串。
解題思路
找到最右邊的d
個@
的下標,然後將其變成.
即可。
神奇的程式碼
n, d = map(int, input().split())
s = list(input().strip())
pos = [i for i in range(n) if s[i] == '@']
pos = pos[-d:]
for i in pos:
s[i] = '.'
s = ''.join(s)
print(s)
C - Kaiten Sushi (abc382 C)
題目大意
給定 \(N\) 個人的美食級別 \(A_i\) 和 \(M\) 塊壽司的美味度 \(B_i\) ,這些壽司依次經過 \(N\) 個人,如果某個人的美食級別不低於壽司的美味度,那麼這個人就會吃掉這塊壽司,問每塊壽司被誰吃掉。
一個人可以吃多塊壽司,但是一塊壽司只能被一個人吃。
解題思路
考慮每塊壽司被誰吃。樸素的想法就是依次考慮每個位置的人,然後找到第一個美味度大於等於這個人的人,這個人就會吃掉這塊壽司。複雜度顯然是 \(O(NM)\) 的。
剛才是依次遍歷每個位置,判斷美味級別,換一種思路,對於每一塊美味度為 \(B_j\) 的壽司,我們其實就是找美味級別\(\leq B_j\)的位置中最小的那個。
每個人表示成一個二元組 \((A_i, i)\) ,其中第二維是位置。然後按照美味度排序,這樣對於每一塊壽司,我們只需要找到滿足\(A_i \leq B_j\)的最小的\(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, m;
cin >> n >> m;
vector<int> a(n);
for (auto& x : a)
cin >> x;
vector<int> id(n);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(), [&](int i, int j) { return a[i] < a[j]; });
vector<int> minn(n);
minn[0] = id[0];
for (int i = 1; i < n; ++i) {
minn[i] = min(minn[i - 1], id[i]);
}
for (int i = 0; i < m; ++i) {
int b;
cin >> b;
auto pos = upper_bound(id.begin(), id.end(), b,
[&](int x, int y) { return a[y] > x; }) -
id.begin();
if (pos == 0) {
cout << -1 << '\n';
} else {
cout << minn[pos - 1] + 1 << '\n';
}
}
return 0;
}
D - Keep Distance (abc382 D)
題目大意
給定兩個整數 \(N\) 和 \(M\) ,按字典序順序,列印所有滿足以下條件的長度為 \(N\) 的整數序列 \((A_1, A_2, \ldots, A_N)\) 。
- \(1 \leq A_i\)
- 從 \(2\) 到 \(N\) 的每個整數 \(i\) 的 \(A_{i - 1} + 10 \leq A_i\)
- \(A_N \leq M\)
解題思路
由於 \(N\) 的範圍很小,所以可以直接暴力搜尋,其中進行一個最優性剪枝,即如果 \(A_{i - 1} + 10 \times (N - i) > M\) ,那麼就不可能滿足條件,直接剪枝。
神奇的程式碼
#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<vector<int>> ans;
vector<int> tmp(n);
auto dfs = [&](auto&& dfs, int pos) -> void {
if (pos == n && tmp[pos - 1] <= m) {
ans.push_back(tmp);
return;
}
int st = pos ? tmp[pos - 1] + 10 : 1;
for (int i = st; i <= m; ++i) {
if (i + 10 * (n - pos - 1) > m)
continue;
tmp[pos] = i;
dfs(dfs, pos + 1);
}
};
dfs(dfs, 0);
cout << ans.size() << '\n';
for (auto& i : ans) {
for (auto& j : i)
cout << j << ' ';
cout << '\n';
}
return 0;
}
E - Expansion Packs (abc382 E)
題目大意
有無數牌包,每包有 \(N\) 張牌。每張牌有 \(P_i\) %的機率是稀有的。每張牌是否稀有是獨立的。
不斷開包,直到至少有 \(X\) 張稀有卡牌時,求開包的預期次數。
解題思路
期望題,考慮倒著的狀態,即設 \(dp[i]\) 表示已有 \(i\) 張稀有卡牌時,為了得到 \(X\) 張稀有卡牌還需要的期望開包次數。
最終條件是 \(dp[X] = 0\) ,已經已經有 \(X\) 張稀有卡牌時,不需要再開包。
然後考慮 \(dp[i]\) 怎麼求,根據期望的定義,當前狀態的期望是所有後續狀態的期望的加權和,其中權重就是轉移機率。
後續狀態是什麼呢?即當前所做的決策的結果,即開包後,我可能得到 \(0, 1, 2, \ldots, N\) 張稀有卡牌,這些都是後續狀態,對應的後續狀態就是 \(dp[i + j]\) ,其中 \(j\) 表示得到 \(j\) 張稀有卡牌。
因此 \(dp[i] = (1 + \sum_{j = 0}^{N} dp[i + j]) p_j\) ,其中 \(p_j\) 表示包裡有 \(j\) 張稀有卡牌的機率。注意這個式子就是期望的定義:當前狀態的期望是所有後續狀態的期望的加權和。注意 \(j=0\) 時,右邊的 \(dp[i + j]\) 是 \(dp[i]\) ,這裡會有迴圈依賴,因此把這一項移動到左邊,得到 \(dp[i] = \frac{1 + \sum_{j = 1}^{N} dp[i + j] p_j}{1 - p_0}\) 。
那現在的問題就是如何求 \(p_j\),即包裡有 \(j\) 張稀有卡牌的機率。這個求法角度可能得用到生成函式的知識。
每一張牌代表一個生成函式\(f_i(x) = P_i + (1 - P_i) x\),這裡 \(P_i\) 是\([0,1]\)的。將所有牌的\(f_i(x)\)相乘,得到的生成函式\(g_j(x)\)的\(x^j\)項的係數就是包裡有\(j\)張稀有卡牌的機率。而多項式乘法直接樸素乘即可,時間複雜度是\(O(N^2)\)。
求出了\(p_j\),就可以直接求出\(dp[i]\)了,時間複雜度也是\(O(N^2)\)。
神奇的程式碼
#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, x;
cin >> n >> x;
vector<int> po(n);
for (auto& i : po)
cin >> i;
vector<double> dp(x + 1, 0);
vector<double> p(n + 1, 0);
p[0] = 1;
for (int i = 0; i < n; ++i) {
vector<double> p1(n + 1, 0), p2(n + 1, 0);
for (int j = 0; j <= n; ++j) {
p1[j] = p[j] * (100 - po[i]) / 100.0;
if (j > 0)
p2[j] = p[j - 1] * po[i] / 100.0;
}
for (int j = 0; j <= n; ++j) {
p[j] = p1[j] + p2[j];
}
}
dp[x] = 0;
for (int i = x - 1; i >= 0; --i) {
for (int j = 1; j <= n; ++j) {
dp[i] += ((i + j > x ? 0 : dp[i + j])) * p[j];
}
dp[i] = (1 + dp[i]) / (1 - p[0]);
}
cout << fixed << setprecision(10) << dp[0] << '\n';
return 0;
}
F - Falling Bars (abc382 F)
題目大意
給定一個 \(H \times W\) 的網格,網格中有 \(N\) 個橫條,橫條長度為\(L_i\),位於\((R_i, C_i), (R_i, C_i + 1), \dots, (R_i, C_i + L_i - 1)\) 。初始時橫條沒有重疊。
然後所有橫條都會往下落( \(R_i\) 增大),如果下面沒有橫條的話,橫條都會往下移動一個單位。否則不會移動。
問最後每個位置的橫條的所處的行。
解題思路
首先考慮 \(R_i\) 最大的橫條,它會落到最下面,因此列 \([C_i, C_i + L_i - 1]\) 的深度變為了 \(H - 1\),依次類推,考慮第 \(i\) 個橫條,其列為 \([C_i, C_i + L_i - 1]\) ,那它最後落到的行數是多少呢?那就是 \([C_i, C_i + L_i - 1]\) 的深度最小值,落完後,更新一下 \([C_i, C_i + L_i - 1]\) 的深度即可。
即維護陣列 \(h[i]\) 表示第 \(i\) 列無橫條的最深深度,然後按照 \(R_i\) 從大到小的順序處理,每次查詢 \(h[C_i, C_i + L_i - 1]\) 深度的最小值\(r\),那該橫條就落在深度為\(r\)上,然後更新 \(h[C_i, C_i + L_i - 1]\) 的深度為 \(r - 1\)。區間查詢和區間修改,用線段樹維護該陣列即可。時間複雜度為 \(O(N \log W)\)。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 2e5 + 8;
const int inf = 1e9 + 7;
class segment {
#define lson (root << 1)
#define rson (root << 1 | 1)
public:
int minn[N << 2];
int lazy[N << 2];
void build(int root, int l, int r, int deep) {
if (l == r) {
minn[root] = deep;
lazy[root] = inf;
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid, deep);
build(rson, mid + 1, r, deep);
minn[root] = min(minn[lson], minn[rson]);
lazy[root] = inf;
}
void pushup(int root) {}
void pushdown(int root) {
if (lazy[root] != inf) {
minn[lson] = min(minn[lson], lazy[root]);
minn[rson] = min(minn[rson], lazy[root]);
lazy[lson] = min(lazy[lson], lazy[root]);
lazy[rson] = min(lazy[rson], lazy[root]);
lazy[root] = inf;
}
}
void update(int root, int l, int r, int L, int R, int val) {
if (L <= l && r <= R) {
minn[root] = min(minn[root], val);
lazy[root] = min(lazy[root], val);
return;
}
pushdown(root);
int mid = (l + r) >> 1;
if (L <= mid)
update(lson, l, mid, L, R, val);
if (R > mid)
update(rson, mid + 1, r, L, R, val);
minn[root] = min(minn[lson], minn[rson]);
}
int query(int root, int l, int r, int L, int R) {
if (L <= l && r <= R) {
return minn[root];
}
pushdown(root);
int mid = (l + r) >> 1;
int resl = inf, resr = inf;
if (L <= mid)
resl = query(lson, l, mid, L, R);
if (R > mid)
resr = query(rson, mid + 1, r, L, R);
return min(resl, resr);
}
} sg;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int h, w, n;
cin >> h >> w >> n;
vector<array<int, 3>> seg(n);
for (auto& [r, c, l] : seg)
cin >> r >> c >> l;
vector<int> id(n);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(),
[&](int i, int j) { return seg[i][0] > seg[j][0]; });
vector<int> ans(n);
sg.build(1, 1, w, h);
for (auto& i : id) {
auto [r, c, l] = seg[i];
ans[i] = sg.query(1, 1, w, c, c + l - 1);
sg.update(1, 1, w, c, c + l - 1, ans[i] - 1);
}
for (auto x : ans)
cout << x << '\n';
return 0;
}
G - Tile Distance 3 (abc382 G)
題目大意
<++>
解題思路
<++>
神奇的程式碼