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\) 个人的美食级别 \(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)

题目大意

<++>

解题思路

<++>

神奇的代码



posted @ 2024-12-07 11:44  ~Lanly~  阅读(46)  评论(0编辑  收藏  举报