atcoder ABC 458 题解

atcoder abc 458 题解

A - Chompers

题目描述

给定一个由小写英文字母组成的字符串 SSS 和一个正整数 NNN。SSS 的长度至少为 2N+12N+12N+1。

请找出从 SSS 的开头移除 NNN 个字符、从结尾移除 NNN 个字符后得到的字符串。

解题思路

我们直接计算在截取之后的字符串长度是多少,然后从要保留的字符串开始的地方开始,用 substr 求解即可。

代码

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

int main()
{
    string s;
    int n;
    cin >> s >> n;
    int len = s.size();
    len -= 2 * n; // 计算中间剩余字符的长度
    cout << s.substr(n, len); // 从第n个字符开始截取len个字符
    return 0;
}

B - Count Adjacent Cells

题目描述

有一个 HHH 行 WWW 列的网格。从上往下数第 iii 行、从左往右数第 jjj 列的单元格记为 (i,j)(i, j)(i,j)。

当 ∣x1−x2∣+∣y1−y2∣=1|x_1 - x_2| + |y_1 - y_2| = 1∣x1−x2∣+∣y1−y2∣=1 时,我们称单元格 (x1,y1)(x_1, y_1)(x1,y1) 和 (x2,y2)(x_2, y_2)(x2,y2) 是边相邻的。

对每个单元格,求出与它边相邻的单元格的数量。

解题思路

我们可以发现,中间的单元格最多有 4 个相邻的格子。对于每个格子,我们先假设答案是 4,然后检查它是否在边界上:如果在第一行或最后一行,就减 1;如果在第一列或最后一列,也减 1。这样就能得到每个格子的相邻数了。

代码

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

int h, w;

int main()
{
    cin >> h >> w;
    for (int i = 1; i <= h; i++, cout << '\n') // 遍历每一行
        for (int j = 1; j <= w; j++) // 遍历每一列
        {
            int ans = 4; // 初始假设最多4个相邻格子
            if (i == 1)
                ans--; // 如果在第一行,上面没有格子
            if (j == 1)
                ans--; // 如果在第一列,左边没有格子
            if (i == h)
                ans--; // 如果在最后一行,下面没有格子
            if (j == w)
                ans--; // 如果在最后一列,右边没有格子
            cout << ans << ' ';
        }
    return 0;
}

C - C Stands for Center

题目描述

给定一个由大写英文字母组成的字符串 SSS。请找出满足以下所有条件的子串(连续子序列)的数量。

  • 子串包含奇数个字符。
  • 它的中间字符是 C。更正式地说,如果提取的子串有 lll 个字符,那么它的第 ((l+1)/2)((l+1)/2)((l+1)/2) 个字符是 C

即使两个子串作为字符串完全相同,但只要它们是从不同位置提取的,就需要分别计数。

解题思路

可以对每个 C 单独考虑,因为每个符合条件的子串都必须以某个 C 为中心。对于位置 i 上的 C,左边最多可以延伸 i 个字符,右边最多可以延伸 s.size() - i - 1 个字符。我们取左右两边较小的那个值,就是以这个 C 为中心的符合条件的子串数量。把所有 C 的情况加起来就是答案了。

代码

cpp 复制代码
#include <bits/stdc++.h>
#define int long long

using namespace std;

string s;

signed main()
{
    cin >> s;
    int ans = 0;
    for (int i = 0; i < s.size(); i++) // 遍历每个字符
        if (s[i] == 'C') // 如果是'C'
            ans += min((int)s.size() - i, i + 1); // 计算以它为中心的子串数量
    cout << ans;
    return 0;
}

D - Chalkboard Median

题目描述

黑板上写着一个整数 XXX。

给定 QQQ 个查询,按顺序处理它们。第 iii 个查询(1≤i≤Q1 \le i \le Q1≤i≤Q)如下:

给出两个整数 AiA_iAi 和 BiB_iBi。在黑板上写下这两个新整数 AiA_iAi 和 BiB_iBi。

然后,输出黑板上写的 2i+12i+12i+1 个整数的中位数。

解题思路

我们可以用两个堆来维护所有数字。一个最大堆用来存较小的那一半数,这样它的顶部就是这一半数里最大的;一个最小堆用来存较大的那一半数,它的顶部就是这一半数里最小的。这样安排的好处是,中位数要么是最小堆的顶部,要么是最大堆的顶部,很容易拿到。

对于总数是奇数的情况,我们让最小堆的大小比最大堆多 1,这样中位数就是最小堆的顶部元素。每次添加新数字时,我们先看它和最大堆顶部的大小关系,如果比它大就放到最小堆,否则放到最大堆。

放完之后我们需要调整两个堆的大小平衡。如果最大堆的大小超过了最小堆,就把最大堆的顶部移到最小堆;如果最小堆的大小比最大堆多了不止 1,就把最小堆的顶部移到最大堆。这样每次调整完,中位数就一定是最小堆的顶部了。

代码

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

int x, q;

int main()
{
    priority_queue<int> q1; // 最大堆,存较小的一半
    priority_queue<int, vector<int>, greater<int>> q2; // 最小堆,存较大的一半
    cin >> x >> q;
    q1.push(x); // 初始数字放入q1
    while (q--)
    {
        int a, b;
        cin >> a >> b;
        // 将a放入合适的堆
        if (a > q1.top())
            q2.push(a);
        else
            q1.push(a);
        // 将b放入合适的堆
        if (b > q1.top())
            q2.push(b);
        else
            q1.push(b);
        // 调整堆的大小,保持q2.size() <= q1.size() + 1
        while (q1.size() > q2.size())
            q2.push(q1.top()), q1.pop();
        while (q2.size() > q1.size() + 1)
            q1.push(q2.top()), q2.pop();
        cout << q2.top() << '\n'; // 中位数就是q2的顶部
    }
    return 0;
}

E - Count 123

题目描述

求满足以下所有条件的序列 A=(a1,⋯ ,aX1+X2+X3)A = (a_1, \cdots, a_{X_1 + X_2 + X_3})A=(a1,⋯,aX1+X2+X3) 的数量,对 998244353998244353998244353 取模。序列的长度为 X1+X2+X3X_1+X_2+X_3X1+X2+X3。

  • AAA 中恰好包含 X1X_1X1 个 111、X2X_2X2 个 222 和 X3X_3X3 个 333。
  • 相邻元素的绝对差不超过 111。即对于所有满足 1≤i≤X1+X2+X3−11 \leq i \leq X_1+X_2+X_3-11≤i≤X1+X2+X3−1 的整数 iii,都有 ∣ai+1−ai∣≤1|a_{i+1} - a_i| \leq 1∣ai+1−ai∣≤1。

解题思路

观察题目条件,相邻元素的差不能超过 1,这意味着 1 和 3 不能直接相邻。我们可以先考虑如何把 1 插入到 2 的里面,然后再放 3。

我们先枚举有 i 个位置插入 1,这 i 个位置要从 x2 + 1 个空位里选(x2 个 2 之间有 x2 + 1 个空位)。选好位置后,我们要把所有的 1 分到这 i 个位置里,用插板法,就是从 x1 - 1 个间隔里选 i - 1 个板子。

最后考虑放 3。因为我们用了 i 个位置放 1,还剩下 x2 - i 个位置可以插入 3。放 3 也可以用插板法,相当于把 x3 个 3 分到 x2 - i + 1 个位置里,也就是从 x3 + x2 - i 个位置里选 x2 - i 个板子。把这三个组合数乘起来,再枚举所有可能的 i 加起来就是答案了。

代码

cpp 复制代码
#include <bits/stdc++.h>
#define int long long

using namespace std;

const int mod = 998244353, maxn = 3e6 + 10;
int x1, x2, x3, fac[maxn]; // fac[i] 是 i! mod mod

int quick_pow(int a, int b) // 快速幂,计算 a^b mod mod
{
    int ans = 1;
    while (b)
    {
        if (b & 1)
            (ans *= a) %= mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

int get_num(int a, int b) // 计算组合数 C(a, b)
{
    if (a < b || a < 0 || b < 0)
        return 0;
    return fac[a] * quick_pow(fac[a - b], mod - 2) % mod * quick_pow(fac[b], mod - 2) % mod;
}

signed main()
{
    cin >> x1 >> x2 >> x3;
    fac[0] = 1;
    for (int i = 1; i < maxn; i++) // 预处理阶乘
        fac[i] = fac[i - 1] * i % mod;
    int ans = 0;
    for (int i = 1; i <= min(x1, x2 + 1); i++) // 枚举i个1被2隔开
    {
        int sum = 1;
        (sum *= get_num(x2 + 1, i)) %= mod; // 选i个位置放分隔的2
        (sum *= get_num(x1 - 1, i - 1)) %= mod; // 把x1个1分成i段
        (sum *= get_num(x3 + x2 - i, x2 - i)) %= mod; // 放x3个3
        ans += sum;
        ans %= mod;
    }
    cout << ans;
    return 0;
}

F - Critical Misread

题目描述

给定 KKK 个由小写英文字母组成的字符串 SiS_iSi。

求长度为 NNN 的、由小写英文字母组成的字符串中,不包含任何 S1,S2,...,SKS_1, S_2, \dots, S_KS1,S2,...,SK 作为子串(连续子序列)的字符串数量,对 998244353998244353998244353 取模。

解题思路

首先把所有禁止的字符串插到 trie 树上,然后建立 AC 自动机的 fail 指针。这样我们就能知道,当我们在某个状态时,添加一个新字符会转移到哪个状态。

我们可以用动态规划来计数。dp[i][j] 表示长度为 i 的字符串,最后在 AC 自动机的状态 j 的合法方案数。转移就是从状态 j 添加一个字符 c,转移到状态 k,如果 j 和 k 都不是禁止状态,就可以转移。

因为 N 可能很大,直接递推会超时,所以我们可以把转移关系表示成矩阵,然后用矩阵快速幂来加速。这样就能在 O((K*L)^3 * logN) 的时间内解决问题(应该没算错),其中 L 是禁止字符串的平均长度。

代码

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

const int MOD = 998244353;
const int maxn = 110;
int trie[maxn][26], fail[maxn], idx; // trie树、fail指针、节点编号
bool ban[maxn]; // 标记该状态是否包含禁止字符串
ll a[maxn][maxn], res[maxn][maxn]; // 转移矩阵和结果矩阵

void insert(char *s) // 把禁止字符串插入trie树
{
    int x = 0;
    for (int i = 0; s[i]; i++)
    {
        int c = s[i] - 'a';
        if (!trie[x][c])
            trie[x][c] = ++idx;
        x = trie[x][c];
    }
    ban[x] = true; // 标记该节点是禁止的
}

void build() // 建立AC自动机的fail指针
{
    queue<int> q;
    for (int i = 0; i < 26; i++)
        if (trie[0][i])
            q.push(trie[0][i]);
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        ban[u] |= ban[fail[u]]; // 如果fail指针指向的状态是禁止的,当前状态也是禁止的
        for (int i = 0; i < 26; i++)
        {
            if (trie[u][i])
            {
                fail[trie[u][i]] = trie[fail[u]][i];
                q.push(trie[u][i]);
            }
            else
            {
                trie[u][i] = trie[fail[u]][i]; // 路径压缩
            }
        }
    }
}

void mul(ll x[][maxn], ll y[][maxn]) // 矩阵乘法
{
    ll z[maxn][maxn] = {0};
    for (int i = 0; i <= idx; i++)
        for (int k = 0; k <= idx; k++)
            for (int j = 0; j <= idx; j++)
                z[i][j] = (z[i][j] + x[i][k] * y[k][j]) % MOD;
    for (int i = 0; i <= idx; i++)
        for (int j = 0; j <= idx; j++)
            x[i][j] = z[i][j];
}

ll solve(int n) // 矩阵快速幂求解
{
    memset(a, 0, sizeof(a));
    memset(res, 0, sizeof(res));
    for (int i = 0; i <= idx; i++)
        res[i][i] = 1; // 单位矩阵
    for (int i = 0; i <= idx; i++) // 构建转移矩阵
    {
        if (ban[i])
            continue;
        for (int j = 0; j < 26; j++)
        {
            if (!ban[trie[i][j]])
                a[i][trie[i][j]]++; // 从i转移到trie[i][j]有1种方式
        }
    }
    while (n) // 快速幂
    {
        if (n & 1)
            mul(res, a);
        mul(a, a);
        n >>= 1;
    }
    ll ans = 0;
    for (int i = 0; i <= idx; i++)
        ans = (ans + res[0][i]) % MOD; // 从初始状态0出发,所有可能的终点
    return ans;
}

int main()
{
    int n, k;
    char s[12];
    scanf("%d%d", &n, &k);
    while (k--)
    {
        scanf("%s", s);
        insert(s);
    }
    build();
    printf("%lld\n", solve(n));
    return 0;
}

G - Children Yearn for the Evil Kindergarten

题目描述

游戏场地有 1010010^{100}10100 个孩子。初始时,没有孩子有任何奖牌。

一个孩子只有在被淘汰逃跑时才会离开场地。

游戏共 NNN 天。在第 iii 天(1≤i≤N1 \leq i \leq N1≤i≤N),按顺序执行以下操作。

  • 收集场地中所有孩子持有的奖牌,设 sss 为收集到的奖牌总数。
  • 将 s+Ais + A_is+Ai 枚奖牌自由分配给场地中的孩子(如果场地中没有孩子,则不做任何操作)。
  • 在场地中的孩子里,奖牌少于 BiB_iBi 的被淘汰。奖牌不少于 BiB_iBi 的孩子各失去 BiB_iBi 枚奖牌。
  • 在场地中的孩子里,奖牌不少于 CiC_iCi 的孩子可以选择此时逃跑或留在场地。

在第 NNN 天结束时仍留在场地的孩子会被淘汰。

求最终逃跑的孩子的最大可能数量。

给定 TTT 组测试数据,请分别求解。

解题思路

这道题可以二分答案。对于某个答案 m,我们可以检查是否存在一种方式让 m 个孩子最终逃跑。如果可以,我们就尝试更大的 m;如果不行,就尝试更小的 m。

关键在于如何高效地检查某个 m 是否可行。我们可以用一些线性函数来表示每个孩子的状态变化,并用凸包来维护这些函数。因为每天的操作都是线性变换,所以可以用斜率来表示状态的变化,用一个双端队列来维护凸包。

我们需要处理的操作包括:分配奖牌、淘汰奖牌不够的孩子、让奖牌够多的孩子逃跑。这些都可以通过维护凸包上的点来实现,确保我们只保留可能得到最优解的状态。最后根据能否在凸包上找到合适的状态来判断 m 是否可行。

代码

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<ll, ll> pr;

const ll INF = 1LL << 60;

ll n;
vector<ll> a, b, c;

ll slope(pr l, pr r) // 计算两点之间的斜率
{
    ll y = r.second - l.second;
    ll x = r.first - l.first;
    return y / x;
}

bool check(ll m) // 检查是否可以让m个孩子逃跑
{
    if (m <= 0)
        return true;
    ll sa = 0, sb = 0; // 线性变换的参数
    deque<pr> dots; // 双端队列维护凸包上的点
    dots.push_back({m, 0});

    auto evaldot = [&](pr p) -> ll { // 计算点p在当前变换下的值
        return sa * p.first + sb + p.second;
    };

    for (ll di = 0; di < n; di++) // 处理每一天
    {
        sb += a[di]; // 分配奖牌的影响
        sa -= b[di]; // 减去B_i的影响

        // 从队尾移除不合法的点(奖牌不够会被淘汰)
        while (dots.size())
        {
            pr p1 = dots.back();
            ll y1 = evaldot(p1);
            if (y1 >= 0)
                break;
            dots.pop_back();
            if (dots.empty())
                break;
            pr p2 = dots.back();
            ll y2 = evaldot(p2);
            if (y2 < 0)
                continue;
            ll ta = slope(p2, p1);
            ll xadd = y2 / (-(sa + ta));
            if (xadd > 0)
                dots.push_back({p2.first + xadd, p2.second + ta * xadd});
            break;
        }

        // 从队头移除不合法的点
        while (dots.size())
        {
            pr p1 = dots.front();
            ll y1 = evaldot(p1);
            if (y1 >= 0)
                break;
            dots.pop_front();
            if (dots.empty())
                break;
            pr p2 = dots.front();
            ll y2 = evaldot(p2);
            if (y2 < 0)
                continue;
            ll ta = slope(p1, p2);
            ll xadd = y2 / (sa + ta);
            if (xadd > 0)
                dots.push_front({p2.first - xadd, p2.second - ta * xadd});
            break;
        }

        if (dots.empty()) // 如果没有合法的点,m不可行
            return false;

        // 维护凸包,处理逃跑的情况
        while (dots.size() >= 2)
        {
            ll s = sa + slope(dots[0], dots[1]);
            if (s >= c[di])
                dots.pop_front();
            else
                break;
        }

        // 计算可以逃跑的孩子数量
        ll xcadd = evaldot(dots[0]) / c[di];
        if (xcadd >= dots[0].first)
            return true; // 足够让m个孩子逃跑
        else if (xcadd > 0)
            dots.push_front({dots[0].first - xcadd, dots[0].second - (c[di] - sa) * xcadd});
    }

    return false;
}

void solve() // 二分答案
{
    ll ok = 0, ng = max(a[0], 0LL) + 1;
    while (ok + 1 < ng)
    {
        ll med = (ok + ng) / 2;
        if (check(med))
            ok = med;
        else
            ng = med;
    }
    printf("%lld\n", ok);
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin >> T;
    while (T--)
    {
        cin >> n;
        a.resize(n);
        b.resize(n);
        c.resize(n);
        for (ll i = 0; i < n; i++)
            cin >> a[i] >> b[i] >> c[i];
        solve();
    }
    return 0;
}
相关推荐
chengO_o1 小时前
STL关联式容器:map 与 set 的使用
c++·stl·set·map·平衡二叉搜索树
AKA__Zas1 小时前
芝士算法 (双指针篇2.0)
java·数据结构·leetcode·学习方法
如竟没有火炬1 小时前
有序矩阵中第K小的元素
数据结构·线性代数·算法·leetcode·矩阵·深度优先
叁散1 小时前
ESP32智能闹钟系统实验报告
单片机·嵌入式硬件·算法
Realdagongzai1 小时前
Linux 6.19.10 内核调度器算法详解
linux·学习·算法·spring·kernel
charlie1145141911 小时前
现代C++特性指南(5)——RAII 深入理解:资源管理的基石
开发语言·c++·现代c++
洛水水2 小时前
【力扣100题】63.最小覆盖子串
算法·leetcode
神仙别闹2 小时前
基于QT(C++)+Sqlite3实现单词消除游戏系统
c++·qt·sqlite
AllData公司负责人2 小时前
亲测丝滑,体验跃迁|AllData通过集成开源项目RustFS,多模态数据存储新范式
java·大数据·数据库·算法·数据分析·rustfs