AtCoder Beginner Contest 415

链接:https://atcoder.jp/contests/abc415

A – Unsupported Type

算法:

    模拟。

思路:

    无。

关键代码:

void solve()
{
    int n, k;
    cin >> n;
    vi a(n + 10);
    cin >> a;

    cin >> k;
    bool ans = 0;
    for (int i = 0; i < n; ++i)
    {
        if (a[i] == k)
            ans = 1;
    }

    if (ans)
        cout << "Yes" << endl;
    else
        cout << "No" << endl;
}

B – Pick Two

算法:

    模拟。

思路:

    无。

关键代码:

void solve()
{
    string s;
    cin >> s;

    int n = s.size();

    vi ans;
    for (int i = 0; i < n; ++i)
        if (s[i] == '#')
            ans.push_back(i + 1);

    for (int i = 0; i < ans.size(); i += 2)
        cout << ans[i] << ',' << ans[i + 1] << endl;
}

C – Mixture

评价:

    什么狗屎题意。

算法:

    bfs。

思路:

    无。

关键代码:

void solve()
{
    int n;
    string S;
    cin >> n >> S;
    int full = (1 << n) - 1;

    vector<char> vis(1 << n, 0);
    queue<int> q;

    vis[0] = 1;
    q.push(0);

    bool ok = 0;
    while (!q.empty())
    {
        int m = q.front();
        q.pop();
        if (m == full)
        {
            ok = 1;
            break;
        }

        for (int i = 0; i < n; i++)
        {
            if (!(m & (1 << i)))
            {
                int m2 = m | (1 << i);
                if (!vis[m2] && S[m2 - 1] == '0')
                {
                    vis[m2] = 1;
                    q.push(m2);
                }
            }
        }
    }

    if (ok)
        cout << "Yes" << endl;
    else
        cout << "No" << endl;
}

D – Get Many Stickers

算法:

    贪心。

思路:

    对每对兑换所需的消耗进行升序排序,从消耗最小的开始计算即可。

关键代码:

void solve()
{
    ll n, m;
    cin >> n >> m;
    vector<ll> a(m + 10), b(m + 10);

    for (int i = 0; i < m; ++i)
        cin >> a[i] >> b[i];

    vector<array<ll, 3>> cnt;
    for (int i = 0; i < m; ++i)
        cnt.push_back({a[i] - b[i], a[i], b[i]});

    sort(all(cnt));

    ll ans = 0;
    ll sum = n;
    for (int i = 0; i < m; ++i)
    {
        if (sum >= cnt[i][1])
        {
            ll t = 0;
            if (sum == cnt[i][1])
                t = 1;
            else
                t = (sum - cnt[i][1]) / cnt[i][0] + 1ll;
            ans += t;
            sum = sum - t * cnt[i][0];
        }
    }

    cout << ans << endl;
}

E – Hungry Takahashi

算法:

    dp。

思路:

    我们使用逆向动态规划,从终点开始反推到起点。

定义状态:

    dp[i][j] 表示:如果高桥在第 i + j + 1 天结束后正好位于格子 (i, j),那么为了确保后续所有天数都不会饿死,他此时手中最少需要持有的金币数。

    之所以是第 i + j + 1 天,是因为从 (0, 0) 开始,每次只能向右或向下走一格,到达 (i, j) 恰好需要走 i + j 步,所以是第 i + j + 1 天。

状态转移:

    每个状态 dp[i][j] 可以更新它的上方格子 (i – 1, j) 和左方格子 (i, j – 1)。

    以 (i, j) 向上转移为例:

  • 从 (i – 1, j) 移动到 (i, j),发生在第 i + j 天(数组下标为 i + j − 1);
  • 在 (i – 1, j),高桥会先收集 A[i – 1][j] 个金币;
  • 然后他要消费 P[i + j − 1] 个金币吃饭;
  • 接着进入 (i, j),而 dp[i][j] 表示他进入该格子前必须手上至少有这么多金币。

    定义 X 为在到达(i – 1, j)之前获得的金币,因此,他在进入 (i – 1, j) 时,手上需要携带的金币满足:

        \(X + G[i-1][j] – P[i+j-1] ≥ dp[i][j]\)

    也就是说:

\(X ≥ dp[i][j] – G[i-1][j] + P[i+j-1]\)

    因为金币数量不能为负,最少需要的 X 是:

\(max(0, dp[i][j] – G[i-1][j] + P[i+j-1])\)

    所以:

\(dp[i-1][j] = min(dp[i-1][j], max(0, dp[i][j] – G[i-1][j] + P[i+j-1]))\)

    同理可得,从 (i, j) 向左方 (i, j – 1) 转移的公式为:

\(dp[i][j-1] = min(dp[i][j-1], max(0, dp[i][j] – G[i][j-1] + P[i+j-1]))\)

    由于 (i – 1, j) 或 (i, j – 1) 可能也会被其他格子更新过,所以我们要在原有 dp 值基础上取 min,保持最优。

终点初始化:

终点为 (H – 1, W – 1),到达这一天是第 H + W − 1 天(对应数组下标 H + W − 2)。

他当天收集 A[H – 1][W – 1] 个金币,然后需要花费 P[H + W − 2] 个金币买食物。

如果收集的金币不足,那差额就必须提前携带。因此:

\(dp[H-1][W-1] = max(0, P[H+W-2] – G[H-1][W-1])\)

最终答案:

    完成逆向 DP 后,dp[0][0] 就是我们要求的答案——从起点出发,为了走完整个过程所需的最小初始金币数。

关键代码:

void solve()
{
    int n, m;
    cin >> n >> m;

    vector<vector<ll>> g(n + 10, vector<ll>(m + 10));
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j)
            cin >> g[i][j];

    vector<int> p(n + m + 10);
    for (int i = 0; i < n + m - 1; ++i)
        cin >> p[i];

    vector<vector<ll>> dp(n + 10, vector<ll>(m + 10, 1e18));
    dp[n - 1][m - 1] = max(0ll, p[n + m - 2] - g[n - 1][m - 1]);
    for (int i = n - 1; ~i; --i)
    {
        for (int j = m - 1; ~j; --j)
        {
            if (i)
                dp[i - 1][j] = min(dp[i - 1][j],
                                   max(0ll, p[i + j - 1] + dp[i][j] - g[i - 1][j]));
            if (j)
                dp[i][j - 1] = min(dp[i][j - 1],
                                   max(0ll, p[i + j - 1] + dp[i][j] - g[i][j - 1]));
        }
    }

    cout << dp[0][0] << endl;
}

F – Max Combo

算法:

    线段树。

思路:

定义线段树节点:

  • l, r:区间左右端点。
  • llen:从区间左边第一个字符开始,有多少个字符与第一个字符相同,直到遇到不同字符为止。
  • rlen:从区间右边最后一个字符开始,有多少个字符与最后一个字符相同,直到遇到不同字符为止。
  • alen:整个区间中,连续相同字符最长的一段的长度。
  • lc, rc:区间左右端点字符。

合并操作(将两个区间合并为一个区间):

  • 新区间的llen:当左区间的全部字符都相等且与右区间的前面几个字符相等,就可将左区间的llen累加上右区间的llen赋值给新区间的rlen。
  • 新区间的rlen:当右区间的全部字符都相等且与左区间的后面几个字符相等,就可将右区间的rlen累加上左区间的rlen赋值给新区间的rlen。
  • 新区间的alen:当左区间的后缀与右区间的前缀相等且长度大于左区间的alen与右区间的alen,就可将新区间的alen更新。

修改操作:

    递归到叶子节点,修改叶子节点信息,自底向上合并区间。

查询操作:

    将区间拆分为线段树节点,合并拆分出的区间。

关键代码:

const int N = 5e5 + 10;

int n, q;
string s;
struct Node
{
    int l, r;    
    int llen;    
    int rlen;    
    int alen;    
    char lc, rc; 
} tr[N << 2];

Node merge(Node a, Node b)
{
    Node ans = {a.l, b.r, a.llen, b.rlen, max({a.alen, b.alen}), a.lc, b.rc};

    if (a.llen == a.r - a.l + 1 && a.rc == b.lc)
        ans.llen += b.llen;

    if (b.rlen == b.r - b.l + 1 && a.rc == b.lc)
        ans.rlen += a.rlen;

    if (a.rc == b.lc)
        ans.alen = max(ans.alen, a.rlen + b.llen);

    return ans;
}

void build(int u, int l, int r)
{
    if (l == r)
    {
        tr[u] = {l, l, 1, 1, 1, s[l], s[l]};
        return;
    }

    tr[u].l = l, tr[u].r = r;
    int mid = l + r >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
    tr[u] = merge(tr[u << 1], tr[u << 1 | 1]);
}

void update(int u, int x, char c)
{
    if (tr[u].l == tr[u].r)
    {
        s[x] = c;
        tr[u].lc = tr[u].rc = c;
        tr[u].llen = tr[u].rlen = tr[u].alen = 1;
        return;
    }

    int mid = tr[u].l + tr[u].r >> 1;
    if (x <= mid)
        update(u << 1, x, c);
    else
        update(u << 1 | 1, x, c);
    tr[u] = merge(tr[u << 1], tr[u << 1 | 1]);
}

Node query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r)
        return tr[u];

    int mid = tr[u].l + tr[u].r >> 1;
    if (r <= mid)
        return query(u << 1, l, r);
    if (l > mid)
        return query(u << 1 | 1, l, r);
    return merge(query(u << 1, l, r), query(u << 1 | 1, l, r));
}

void solve()
{
    cin >> n >> q >> s;

    s = ' ' + s;
    build(1, 1, n);

    while (q--)
    {
        int op;
        cin >> op;
        if (op == 1)
        {
            int pos;
            char x;
            cin >> pos >> x;
            update(1, pos, x);
        }
        else
        {
            int l, r;
            cin >> l >> r;
            cout << query(1, l, r).alen << endl;
        }
    }
}

G – Get Many Cola

还没补。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇