AtCoder Beginner Contest 382

省流版
  • A. 统计计数即可
  • B. 模拟更改即可
  • C. 考虑每个寿司,找到满足条件的位置最小值,即一个前缀最小值
  • D. 搜索剪枝即可
  • E. 期望题,根据期望定义写出转移式,从生成函数的角度求翻出 i 张稀有牌的概率
  • F. 从深度最大的横条开始,考虑下落时求解的每列深度然后更新,用线段树维护即可

题目大意

给定一个包含@.的长度为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)


题目大意

给定一个包含@.的长度为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 个人的美食级别 AiM 块寿司的美味度 Bi ,这些寿司依次经过 N 个人,如果某个人的美食级别不低于寿司的美味度,那么这个人就会吃掉这块寿司,问每块寿司被谁吃掉。

一个人可以吃多块寿司,但是一块寿司只能被一个人吃。

解题思路

考虑每块寿司被谁吃。朴素的想法就是依次考虑每个位置的人,然后找到第一个美味度大于等于这个人的人,这个人就会吃掉这块寿司。复杂度显然是 O(NM) 的。

刚才是依次遍历每个位置,判断美味级别,换一种思路,对于每一块美味度为 Bj 的寿司,我们其实就是找美味级别Bj的位置中最小的那个。

每个人表示成一个二元组 (Ai,i) ,其中第二维是位置。然后按照美味度排序,这样对于每一块寿司,我们只需要找到满足AiBj的最小的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)

题目大意

给定两个整数 NM ,按字典序顺序,打印所有满足以下条件的长度为 N 的整数序列 (A1,A2,,AN)

  • 1Ai
  • 2N 的每个整数 iAi1+10Ai
  • ANM

解题思路

由于 N 的范围很小,所以可以直接暴力搜索,其中进行一个最优性剪枝,即如果 Ai1+10×(Ni)>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 张牌。每张牌有 Pi %的概率是稀有的。每张牌是否稀有是独立的。

不断开包,直到至少有 X 张稀有卡牌时,求开包的预期次数。

解题思路

期望题,考虑倒着的状态,即设 dp[i] 表示已有 i 张稀有卡牌时,为了得到 X 张稀有卡牌还需要的期望开包次数。

最终条件是 dp[X]=0 ,已经已经有 X 张稀有卡牌时,不需要再开包。

然后考虑 dp[i] 怎么求,根据期望的定义,当前状态的期望是所有后续状态的期望的加权和,其中权重就是转移概率。

后续状态是什么呢?即当前所做的决策的结果,即开包后,我可能得到 0,1,2,,N 张稀有卡牌,这些都是后续状态,对应的后续状态就是 dp[i+j] ,其中 j 表示得到 j 张稀有卡牌。

因此 dp[i]=(1+j=0Ndp[i+j])pj ,其中 pj 表示包里有 j 张稀有卡牌的概率。注意这个式子就是期望的定义:当前状态的期望是所有后续状态的期望的加权和。注意 j=0 时,右边的 dp[i+j]dp[i] ,这里会有循环依赖,因此把这一项移动到左边,得到 dp[i]=1+j=1Ndp[i+j]pj1p0

那现在的问题就是如何求 pj,即包里有 j 张稀有卡牌的概率。这个求法角度可能得用到生成函数的知识。

每一张牌代表一个生成函数fi(x)=Pi+(1Pi)x,这里 Pi[0,1]的。将所有牌的fi(x)相乘,得到的生成函数gj(x)xj项的系数就是包里有j张稀有卡牌的概率。而多项式乘法直接朴素乘即可,时间复杂度是O(N2)

求出了pj,就可以直接求出dp[i]了,时间复杂度也是O(N2)

神奇的代码
#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×W 的网格,网格中有 N 个横条,横条长度为Li,位于(Ri,Ci),(Ri,Ci+1),,(Ri,Ci+Li1) 。初始时横条没有重叠。

然后所有横条都会往下落( Ri 增大),如果下面没有横条的话,横条都会往下移动一个单位。否则不会移动。

问最后每个位置的横条的所处的行。

解题思路

首先考虑 Ri 最大的横条,它会落到最下面,因此列 [Ci,Ci+Li1] 的深度变为了 H1,依次类推,考虑第 i 个横条,其列为 [Ci,Ci+Li1] ,那它最后落到的行数是多少呢?那就是 [Ci,Ci+Li1] 的深度最小值,落完后,更新一下 [Ci,Ci+Li1] 的深度即可。

即维护数组 h[i] 表示第 i 列无横条的最深深度,然后按照 Ri 从大到小的顺序处理,每次查询 h[Ci,Ci+Li1] 深度的最小值r,那该横条就落在深度为r上,然后更新 h[Ci,Ci+Li1] 的深度为 r1。区间查询和区间修改,用线段树维护该数组即可。时间复杂度为 O(NlogW)

神奇的代码
#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)

题目大意

<++>

解题思路

<++>

神奇的代码


posted @   ~Lanly~  阅读(92)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示