|
| 1 | +## [A - Daily Cookie](https://atcoder.jp/contests/abc382/tasks/abc382_a) |
| 2 | + |
| 3 | +???+ Abstract "题目大意" |
| 4 | + |
| 5 | + 有 $N$ 个盒子,给你一个长度为 $N$ 的字符串 $S$,如果 $S_i$ 是 `@`,表示第 $i$ 个盒子有一块饼干,如果 $S_i$ 是 `.`,表示第 $i$ 个盒子是空的。如果吃掉 $D$ 块饼干(保证 $N$ 个盒子中至少有 $D$ 块饼干),还剩多少块饼干? |
| 6 | + |
| 7 | +??? Success "参考代码" |
| 8 | + |
| 9 | + === "C++" |
| 10 | + |
| 11 | + ```c++ |
| 12 | + #include <algorithm> |
| 13 | + #include <iostream> |
| 14 | + #include <string> |
| 15 | + |
| 16 | + using namespace std; |
| 17 | + |
| 18 | + int main() |
| 19 | + { |
| 20 | + int n, d; |
| 21 | + string s; |
| 22 | + cin >> n >> d >> s; |
| 23 | + int cnt = count(s.begin(), s.end(), '.'); |
| 24 | + cout << cnt + d << endl; |
| 25 | + return 0; |
| 26 | + } |
| 27 | + ``` |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +## [B - Daily Cookie 2](https://atcoder.jp/contests/abc382/tasks/abc382_b) |
| 32 | + |
| 33 | +???+ Abstract "题目大意" |
| 34 | + |
| 35 | + 有 $N$ 个盒子,给你一个长度为 $N$ 的字符串 $S$,如果 $S_i$ 是 `@`,表示第 $i$ 个盒子有一块饼干,如果 $S_i$ 是 `.`,表示第 $i$ 个盒子是空的。有人打算吃掉 $D$ 块饼干(保证 $N$ 个盒子中至少有 $D$ 块饼干),他每一次会选择最靠右且有饼干的盒子,吃掉其中的饼干。在他吃掉 $D$ 块饼干后, 从左到右输出 $N$ 个盒子是否有饼干。 |
| 36 | + |
| 37 | +??? Success "参考代码" |
| 38 | + |
| 39 | + === "C++" |
| 40 | + |
| 41 | + ```c++ |
| 42 | + #include <iostream> |
| 43 | + #include <string> |
| 44 | + |
| 45 | + using namespace std; |
| 46 | + |
| 47 | + int main() |
| 48 | + { |
| 49 | + int n, d; |
| 50 | + string s; |
| 51 | + cin >> n >> d >> s; |
| 52 | + for(int i = n - 1; i >= 0 && d; i--) |
| 53 | + if(s[i] == '@') |
| 54 | + { |
| 55 | + d--; |
| 56 | + s[i] = '.'; |
| 57 | + } |
| 58 | + cout << s << endl; |
| 59 | + return 0; |
| 60 | + } |
| 61 | + ``` |
| 62 | + |
| 63 | +--- |
| 64 | + |
| 65 | +## [C - Kaiten Sushi](https://atcoder.jp/contests/abc382/tasks/abc382_c) |
| 66 | + |
| 67 | +???+ Abstract "题目大意" |
| 68 | + |
| 69 | + 有 $N(1 \le N \le 2 \times 10 ^ 5)$ 去吃寿司,不同的寿司有不同的美味度,第 $i$ 个人对寿司美味度的要求是至少为 $A_i$,即,如果第 $i$ 个人看见了美味度大于等于 $A_i$ 的寿司,就会吃掉。 |
| 70 | + |
| 71 | + 这 $N$ 个人从左到右(第 $1$ 个人在最左,第 $N$ 个人在最右)坐成一行,寿司在传送带上,传送带的方向是从左到右的,因此寿司总是先被第 $1$ 个人看见。一共有 $M(1 \le M \le 2 \times 10 ^ 5)$ 个寿司,第 $i$ 个寿司的美味度是 $B_i$。如果第 $i$ 个人看见了美味度大于等于 $A_i$ 的寿司,就会吃掉这个寿司,被吃掉的寿司不会被后面的人看见。如果第 $i$ 个人看见了美味度小于 $A_i$ 的寿司,会无视掉这个寿司,随后这个寿司将被下一个人看见。每个人可以吃下无数个寿司。 |
| 72 | + |
| 73 | + 问:这个 $M$ 个寿司分别是被谁吃下的?按顺序输出第 $i$ 个寿司是被哪个人吃下的。(如果某个寿司没有任何人吃,输出 `-1`) |
| 74 | + |
| 75 | +??? Note "解题思路" |
| 76 | + |
| 77 | + 第 $i$ 个人的美味度要求是 $A_i$,则所有美味度大于等于 $A_i$ 的寿司都会被他吃掉,因此后面大于等于 $A_i$ 的人都会被屏蔽掉。所以只需要从 $A_i$ 开始,求出连续递减的子序列,则只有这个子序列的人会吃到寿司。 |
| 78 | + |
| 79 | +??? Success "参考代码" |
| 80 | + |
| 81 | + === "C++" |
| 82 | + |
| 83 | + ```c++ |
| 84 | + #include <functional> |
| 85 | + #include <iostream> |
| 86 | + #include <limits> |
| 87 | + #include <map> |
| 88 | + |
| 89 | + using namespace std; |
| 90 | + |
| 91 | + int main() |
| 92 | + { |
| 93 | + int n, m; |
| 94 | + cin >> n >> m; |
| 95 | + map<int, int, greater<int>> pos; |
| 96 | + for (int i = 1, last = numeric_limits<int>::max(); i <= n; i++) |
| 97 | + { |
| 98 | + int a; |
| 99 | + cin >> a; |
| 100 | + if(a < last) |
| 101 | + { |
| 102 | + pos[a] = i; |
| 103 | + last = a; |
| 104 | + } |
| 105 | + } |
| 106 | + while (m--) |
| 107 | + { |
| 108 | + int b; |
| 109 | + cin >> b; |
| 110 | + auto it = pos.lower_bound(b); |
| 111 | + cout << (it == pos.end() ? -1 : it->second) << endl; |
| 112 | + } |
| 113 | + return 0; |
| 114 | + } |
| 115 | + ``` |
| 116 | + |
| 117 | +--- |
| 118 | + |
| 119 | +## [D - Keep Distance](https://atcoder.jp/contests/abc382/tasks/abc382_d) |
| 120 | + |
| 121 | +???+ Abstract "题目大意" |
| 122 | + 给你两个整数 $N(2 \le N \le 12)$ 和 $M(10N-9 \le M \le 10N)$ |
| 123 | + |
| 124 | + 按照字典序输出所有满足以下条件的序列 $(A_1, A_2, ..., A_N)$: |
| 125 | + |
| 126 | + - $1 \le A_i$ |
| 127 | + - 对于所有的 $i \in [2, N]$,有 $A_{i-1} + 10 \le A_i$ |
| 128 | + - $A_N \le M$ |
| 129 | + |
| 130 | +??? Note "解题思路" |
| 131 | + |
| 132 | + dfs 模板题。 |
| 133 | + |
| 134 | +??? Success "参考代码" |
| 135 | + |
| 136 | + === "C++" |
| 137 | + |
| 138 | + ```c++ |
| 139 | + #include <iostream> |
| 140 | + #include <vector> |
| 141 | + |
| 142 | + using namespace std; |
| 143 | + |
| 144 | + int main() |
| 145 | + { |
| 146 | + int n, m; |
| 147 | + cin >> n >> m; |
| 148 | + vector<vector<int>> ans; |
| 149 | + vector<int> now(n); |
| 150 | + |
| 151 | + auto dfs = [&ans, &now, n, m](auto &self, int cur, int last) -> void |
| 152 | + { |
| 153 | + if (cur == n) |
| 154 | + { |
| 155 | + ans.push_back(now); |
| 156 | + return; |
| 157 | + } |
| 158 | + |
| 159 | + int cnt = n - cur - 1; |
| 160 | + for (int i = last; i + 10 * cnt <= m; i++) |
| 161 | + { |
| 162 | + now[cur] = i; |
| 163 | + self(self, cur + 1, 10 + i); |
| 164 | + } |
| 165 | + }; |
| 166 | + |
| 167 | + dfs(dfs, 0, 1); |
| 168 | + cout << ans.size() << endl; |
| 169 | + for(auto &v : ans) |
| 170 | + { |
| 171 | + for(auto x : v) |
| 172 | + cout << x << ' '; |
| 173 | + cout << endl; |
| 174 | + } |
| 175 | + return 0; |
| 176 | + } |
| 177 | + ``` |
| 178 | + |
| 179 | +--- |
| 180 | + |
| 181 | +## [E - Expansion Packs](https://atcoder.jp/contests/abc382/tasks/abc382_e) |
| 182 | + |
| 183 | +???+ Abstract "题目大意" |
| 184 | + |
| 185 | + 有无限多个卡包,每个卡包有 $N(1 \le N \le 5000)$ 张卡。在每个卡包中,第 $i$ 张卡有 $P_i\%$ 的概率是稀有卡,其中 $1 \le P_i \le 100$。每张卡是否稀有相互独立。 |
| 186 | + |
| 187 | + 你将一直打开新卡包,并获得每个打开的卡包中的所有卡,直到累计获得至少 $X(1 \le X \le 5000)$ 张稀有卡为止。 |
| 188 | + |
| 189 | + 问:当你停下时,打开的卡包数量的数学期望是多少? |
| 190 | + |
| 191 | +??? Note "解题思路" |
| 192 | + |
| 193 | + 因为每个卡包内的每一张卡的稀有概率是固定的,可以处理出一个 $g[i][j]$ 表示在一个卡包的前 $i$ 张卡中开出 $j$ 张稀有卡的概率,显然有 $g[i][j] = g[i][j] \times \frac{100-P_i}{100} + g[i-1][j-1] \times \frac{P_i}{100}$,可以在 $O(N^2)$ 的时间预处理出来,则 $g[N][j]$ 就表示开一个卡包获得 $j$ 张稀有卡的概率。 |
| 194 | + |
| 195 | + 设 $dp[i]$ 表示累计获得至少 $i$ 张稀有卡需要打开卡包数量的数学期望,初始的时候有 $dp[0] = 0$,转移时,枚举新打开一个卡包能获得多少张稀有卡,一个卡包有 $N$ 张卡,最多可获得 $N$ 张稀有卡,最少获得 $0$ 张,因此枚举 $0 \le j \le N$,即:$dp[i] = 1 + \sum\limits_{j=0}^Ndp[i-j]g[N][j]$ |
| 196 | + |
| 197 | + 然后我们会发现两个问题,首先 $i-j$ 是有可能小于 $0$ 的,这可以通过 $\max(i-j, 0)$ 避免,也就时 $dp[i] = 1 + \sum\limits_{j=0}^Ndp[\max(i-j, 0)]g[N][j]$,另一个问题是,当 $j = 0$ 时,右式会出现 $dp[i]$,但是这个式子就是为了求出 $dp[i]$ 的,所以我们变换一下: |
| 198 | + |
| 199 | + $$ |
| 200 | + \begin{align*} |
| 201 | + dp[i] & = 1 + \sum\limits_{j=0}^Ndp[\max(i-j, 0)]g[N][j] \\ |
| 202 | + dp[i] & = 1 + \sum\limits_{j=1}^Ndp[\max(i-j, 0)]g[N][j] + dp[i]g[N][0] \\ |
| 203 | + (1-g[N][0])dp[i] & = 1 + \sum\limits_{j=1}^Ndp[\max(i-j, 0)]g[N][j] \\ |
| 204 | + dp[i] & = \frac{1 + \sum\limits_{j=1}^Ndp[\max(i-j, 0)]g[N][j]}{1-g[N][0]} \\ |
| 205 | + \end{align*} |
| 206 | + $$ |
| 207 | + |
| 208 | + 又因为 $1 \le p \le 100$,所以 $g[N][0]$ 一定不等于 $1$,因此这个变换是合法的。这样就能算了,预处理 $g$ 的时间复杂度是 $O(N^2)$,计算 $dp$ 的时间复杂度是 $O(NX)$,总复杂度就是 $O(N^2+NX)$ |
| 209 | + |
| 210 | +??? Success "参考代码" |
| 211 | + |
| 212 | + === "C++" |
| 213 | + |
| 214 | + ```c++ |
| 215 | + #include <iomanip> |
| 216 | + #include <iostream> |
| 217 | + #include <vector> |
| 218 | + |
| 219 | + using namespace std; |
| 220 | + |
| 221 | + int main() |
| 222 | + { |
| 223 | + int n, x; |
| 224 | + cin >> n >> x; |
| 225 | + vector<double> g(n+1); |
| 226 | + g[0] = 1; |
| 227 | + for(int i = 1; i <= n; i++) |
| 228 | + { |
| 229 | + double p; |
| 230 | + cin >> p; |
| 231 | + for(int j = i; j; j--) |
| 232 | + g[j] = g[j] * (100-p)/100 + g[j-1] * p / 100; |
| 233 | + g[0] *= (100-p)/100; |
| 234 | + } |
| 235 | + vector<double> dp(x+1); |
| 236 | + for(int i = 1; i <= x; i++) |
| 237 | + { |
| 238 | + double sum = 1; |
| 239 | + for(int j = 1; j <= n; j++) |
| 240 | + sum += dp[max(i-j, 0)] * g[j]; |
| 241 | + dp[i] = sum / (1 - g[0]); |
| 242 | + } |
| 243 | + cout << setprecision(8) << dp[x] << endl; |
| 244 | + return 0; |
| 245 | + } |
| 246 | + ``` |
| 247 | + |
| 248 | +--- |
| 249 | + |
| 250 | +## [F - Falling Bars](https://atcoder.jp/contests/abc382/tasks/abc382_f) |
| 251 | + |
| 252 | +???+ Abstract "题目大意" |
| 253 | + |
| 254 | + 有一个 $H \times W(1 \le H, W \le 2 \times 10 ^ 5)$ 的网格。网格中有 $N(1 \le N \le 2 \times 10 ^ 5)$ 个高度为 $1$ 格的方块,每个方块的初始位置用三个数字 $(R_i, C_i, L_i)$ 来描述,表示这个方块占据了 $(R_i, C_i), (R_i, C_i+1), ..., (R_i, C_i+L-1)$ 的位置。输入数据保证这 $N$ 个方块是不重叠的。 |
| 255 | + |
| 256 | + 初始的时间是 $t=0$,在每个 $t=0.5+n$(其中 $n$ 表示自然数)的时刻,会发生以下事情: |
| 257 | + |
| 258 | + - 首先,按顺序遍历所有的方块,对于所有的 $i = 1, 2, ..., N$,假设第 $i$ 个方块当前的位置是 $(R_i, C_i), (R_i, C_i+1), ..., (R_i, C_i+L-1)$,如果 $R_i + 1 \le W$,且 $(R_i+1, C_i), (R_i + 1, C_i+1), ..., (R_i + 1, C_i+L-1)$ 都没有被占据,则第 $i$ 个方块整体向下移动 $1$ 格,占据这些位置。即:如果第 $i$ 个方块没有到达网格的底部,且方块下方 $1$ 格没有被占据,就整体下移 $1$ 格。 |
| 259 | + - 否则,如果第 $i$ 个方块处于网格的底部,或者其下方 $1$ 格的位置被占据,则跳过第 $i$ 个方块的操作。 |
| 260 | + |
| 261 | +  |
| 262 | + |
| 263 | + 问:在 $t = 10^{100}$,这 $N$ 个方块的 $R_i$ 分别是多少?例子见上图。 |
| 264 | + |
| 265 | +??? Note "解题思路" |
| 266 | + |
| 267 | + 可以发现,像上图的这样的情况,无论这个网格有多大,第 $2$ 个方块都会保持在最底端,所以为了加快计算,不能一格一格的移动,应该一次性移动多格。为了移动多格,我们需要算出某一段最高的可被覆盖的位置,这可以用线段树维护一个区间最小值实现。 |
| 268 | + |
| 269 | + 具体来说,我们用 $h[i]$ 表示第 $i$ 列最上方未被占据的行号,初始值就是 $h[i] = W$,当某个方块下落时,假设当前方块占据的是 $(R_i, C_i), (R_i, C_i+1), ..., (R_i, C_i+L-1)$,就查询 $h[C_i]$ 到 $h[C_i + L_i - 1]$ 这一个区间的最小值,模拟下落被卡住的情况。确定下落的位置后,方块占据这一段,因此这一段的值也要修改,涉及到区间修改和区间最小值查询,所以用线段树维护这个 $h$ 数组即可。 |
| 270 | + |
| 271 | + 实际操作的时候,还可以注意到,一定是越靠近底部的方块先确定最终位置,所以按照 $R_i$ 降序排序即可,这样每一次操作都可以确定一个方块的最终位置,一共 $N$ 个方块,单次线段树的修改和查询操作时间复杂度是 $O(\log W)$,因此总的时间复杂度就是 $O(N\log W)$ |
| 272 | + |
| 273 | +??? Success "参考代码" |
| 274 | + |
| 275 | + === "C++" |
| 276 | + |
| 277 | + ```c++ |
| 278 | + #include <algorithm> |
| 279 | + #include <iostream> |
| 280 | + #include <limits> |
| 281 | + #include <vector> |
| 282 | + |
| 283 | + using namespace std; |
| 284 | + |
| 285 | + struct SegTree |
| 286 | + { |
| 287 | + struct Node |
| 288 | + { |
| 289 | + int m; |
| 290 | + bool lazy = false; |
| 291 | + Node(int x) : m(x) {} |
| 292 | + }; |
| 293 | + |
| 294 | + vector<Node> t; |
| 295 | + |
| 296 | + SegTree(int n, int val) : t(4 * n, val) {} |
| 297 | + |
| 298 | + void push_down(int p) |
| 299 | + { |
| 300 | + if (t[p].lazy) |
| 301 | + { |
| 302 | + int lch = p * 2, rch = p * 2 + 1; |
| 303 | + t[lch].m = t[rch].m = t[p].m; |
| 304 | + t[lch].lazy = t[rch].lazy = true; |
| 305 | + t[p].lazy = false; |
| 306 | + } |
| 307 | + } |
| 308 | + |
| 309 | + int query(int p, int beg, int end, int l, int r) |
| 310 | + { |
| 311 | + if (end < l || beg > r) |
| 312 | + return numeric_limits<int>::max(); |
| 313 | + if (beg >= l && end <= r) |
| 314 | + return t[p].m; |
| 315 | + push_down(p); |
| 316 | + int mid = (beg + end) / 2; |
| 317 | + int lch = p * 2, rch = p * 2 + 1; |
| 318 | + return min(query(lch, beg, mid, l, r), query(rch, mid + 1, end, l, r)); |
| 319 | + } |
| 320 | + |
| 321 | + void update(int p, int beg, int end, int l, int r, int x) |
| 322 | + { |
| 323 | + if (end < l || beg > r) |
| 324 | + return; |
| 325 | + if (beg >= l && end <= r) |
| 326 | + { |
| 327 | + t[p].m = x; |
| 328 | + t[p].lazy = true; |
| 329 | + return; |
| 330 | + } |
| 331 | + push_down(p); |
| 332 | + int mid = (beg + end) / 2; |
| 333 | + int lch = p * 2, rch = p * 2 + 1; |
| 334 | + update(lch, beg, mid, l, r, x); |
| 335 | + update(rch, mid + 1, end, l, r, x); |
| 336 | + t[p].m = min(t[p].m, x); |
| 337 | + } |
| 338 | + }; |
| 339 | + |
| 340 | + struct Bar |
| 341 | + { |
| 342 | + int depth, left, right, id; |
| 343 | + }; |
| 344 | + |
| 345 | + int main() |
| 346 | + { |
| 347 | + int h, w, n; |
| 348 | + cin >> h >> w >> n; |
| 349 | + SegTree t(w, h); |
| 350 | + vector<Bar> bars(n); |
| 351 | + for (int i = 0; i < n; i++) |
| 352 | + { |
| 353 | + int r, c, l; |
| 354 | + cin >> r >> c >> l; |
| 355 | + bars[i] = {r, c, c + l - 1, i}; |
| 356 | + } |
| 357 | + sort(bars.begin(), bars.end(), [](const Bar &x, const Bar &y) -> bool |
| 358 | + { return x.depth > y.depth; }); |
| 359 | + vector<int> ans(n); |
| 360 | + for (auto b : bars) |
| 361 | + { |
| 362 | + int m = t.query(1, 1, w, b.left, b.right); |
| 363 | + ans[b.id] = m; |
| 364 | + t.update(1, 1, w, b.left, b.right, m - 1); |
| 365 | + } |
| 366 | + for (auto x : ans) |
| 367 | + cout << x << endl; |
| 368 | + return 0; |
| 369 | + } |
| 370 | + ``` |
| 371 | + |
| 372 | +--- |
| 373 | + |
0 commit comments