AtCoder Beginner Contest 471 ABCDEF

A - Nine or Nein

  • 预估难度:入门
  • 标签:条件分支

题意

给定两个正整数 \(A\)\(B\)

如果以下值中至少有一个等于 \(9\),请输出 Nine;否则,请输出 Nein

  • \(A + B\)
  • \(A - B\)
  • \(A \times B\)
  • \(A \div B\)

数据范围

  • \(1 \leq A \leq 100\)
  • \(1 \leq B \leq 100\)

代码

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int a, b;
    cin >> a >> b;
    if(a + b == 9 || a - b == 9 || a * b == 9 || a == 9 * b)
        cout << "Nine";
    else
        cout << "Nein";
    
    return 0;
}

B - Survey Tabulation

  • 预估难度:普及-
  • 标签:字符串、计数思想

题意

高桥正在统计一项调查的结果。

共有 \(N\) 人参与了调查,第 \(i\) 个人的回答是一个由英文字母组成的字符串 \(S_i\)

请找出本次调查中,给出相同回答的最多人数。

注意:回答中的字母不区分大小写。
例如,AtCoderATCODERatcoder 均被视为相同的回答。

数据范围

  • \(1 \leq N \leq 100\)
  • \(S_i\) 仅包含大小写英文字母,且长度不超过 \(10\)

思路

将所有字符串统一转为大写或小写,然后根据计数思想,借助 map 或是手动维护 string 数组对每种字符串的出现次数进行统计即可。

代码

#include<bits/stdc++.h>
using namespace std;

int main()
{
    map<string, int> cnt;
    
    int n, ans = 0;
    cin >> n;
    while(n--)
    {
        string s;
        cin >> s;
        
        // 全部转大写
        for(int i = 0; i < s.size(); i++)
            if(s[i] >= 'a' && s[i] <= 'z')
                s[i] = s[i] - 'a' + 'A';
        
        // 记录出现次数 并取最大值
        ans = max(ans, ++cnt[s]);
    }
    
    cout << ans;
    
    return 0;
}

C - Cookies and Greedy Takahashi

  • 预估难度:普及-
  • 标签:排序、双指针

题意

在数轴上有 \(N\) 个位置放有饼干,第 \(i\) 块饼干的坐标为 \(A_i\)

高桥最初位于数轴上的坐标 \(0\) 处,他将重复执行以下动作,直到捡起所有的 \(N\) 块饼干:

  • 动作:移动到距离当前位置最近的饼干所在的坐标(如果存在多块这样的饼干,则选择坐标最小的那一块),并捡起该饼干。

请计算高桥在捡起所有饼干的过程中所移动的总距离。

数据范围

  • \(1 \leq N \leq 3\times 10^5\)
  • \(-10^9 \leq A_i \leq 10^9\)
  • \(A_i\neq 0\)\(A_i\) 互不相同

思路

记高桥过程中所在位置为 \(x\)

首先根据“\(A_i \ne 0\)\(A_i\) 互不相同”这两个条件可以得知,如果有多块饼干与坐标 \(x\) 的距离相同,这样的饼干有且只有两块,且一左一右。如果出现这样的情况,我们只需要优先拿左边(坐标较小)的那块即可。

因为高桥初始位置 \(x = 0\),可以发现在过程中,如果高桥下一步选择往左捡饼干,那么这块饼干只可能是负半轴坐标最大的饼干;如果选择往右捡饼干,那么这块饼干只可能是正半轴坐标最小的饼干这两者之一。

因此我们只需要先对所有饼干按坐标排序,然后双指针维护满足 \(A_l \lt 0\) 且还没被捡走的饼干的最大下标 \(l\),以及满足 \(A_r \gt 0\) 且还没被捡走的饼干的最小下标 \(r\),即可直接 \(O(N)\) 模拟。

时间复杂度 \(O(N\log N)\)

代码

#include<bits/stdc++.h>
using namespace std;

int n, a[300005];

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a + 1, a + n + 1);
    
    int r = upper_bound(a + 1, a + n + 1, 0) - a; // > 0 的最小下标
    int l = r - 1; // < 0 的最大下标
    
    int x = 0; // 当前坐标
    
    long long ans = 0;
    
    // 两边饼干都还没捡完
    while(l >= 1 && r <= n)
    {
        if(x - a[l] <= a[r] - x) // 左边更近
        {
            ans += x - a[l];
            x = a[l--];
        }
        else // 右边更近
        {
            ans += a[r] - x;
            x = a[r++];
        }
    }
    
    // 如果某一边还没捡完,直接按顺序捡
    while(l >= 1)
    {
        ans += x - a[l];
        x = a[l--];
    }
    while(r <= n)
    {
        ans += a[r] - x;
        x = a[r++];
    }
    
    cout << ans;
    
    return 0;
}

D - Chargers

  • 预估难度:普及
  • 标签:优先队列、数学

题意

有一个拥有无限个充电槽的充电器。在时间 \(0\) 时,所有充电槽均为空。

每块电池的最大容量为 \(V\)。当电池插入充电槽时,其电量会以 \(1\) 的速率进行充电(即每经过 \(1\) 单位时间,电量增加 \(1\)),直到电量达到最大容量。

请按顺序处理 \(Q\) 个查询。
\(q\) 个查询的格式为以下两种之一,保证 \(t_1 < \dots < t_Q\)

  • 类型 1\(1\) \(t_q\) \(w_q\)):在时间 \(t_q\),将一块电量为 \(w_q\) 的电池插入一个充电槽。
  • 类型 2\(2\) \(t_q\)):在时间 \(t_q\),将当前电量最高的电池从充电槽中拔出,并输出该电池的电量。如果没有任何电池插在充电槽中,则输出 -1

数据范围

  • \(1 \leq Q \leq 3 \times 10^5\)
  • \(1 \leq V \leq 10^9\)
  • 对于第 1 种询问: \(1 \leq t_q \leq 10^9\)\(0 \leq w_q \leq V\)
  • 对于第 2 种询问: \(1 \leq t_q \leq 10^9\)
  • \(t_1 < \dots < t_Q\)

思路

如果我们在时间 \(x\) 将一块电量为 \(w\) 的电池插入充电槽,然后在时间 \(y\) 将其取出,暂不考虑最大容量的限制,那么其电量可以描述为 \(w + y - x\)

由于每次询问 2 会给定一个终止充电的时间,然后询问电量最大的电池,也就相当于上式的 \(y\) 是给定的。那么为了找出 \(w+y-x\) 最大的电池,我们只需要在所有充电槽中找出 \(w - x\) 最大的那一块电池即可。

因此,每当有一块电量为 \(w\) 的电池在时间 \(x\) 插入充电槽,我们只需要借助容器直接维护 \(w-x\) 这一数值,能够实现每次取最大值并快速移除即可。这里可以借助优先队列或是 multiset

最后考虑最大容量的限制,可以发现当 \(w+y-x \gt V\) 时,此时我们需要将电池电量直接当 \(V\) 看,但这并不影响我们每次取最大值的操作,只需要在最后输出实际电量时记得和 \(V\) 取个 \(\min\) 再输出即可。

时间复杂度 \(O(Q\log Q)\)

代码

#include<bits/stdc++.h>
using namespace std;

int main()
{
    priority_queue<int> pq; // 维护 初始电量-开始充电时间 的最大值
    
    int Q, V;
    cin >> Q >> V;
    while(Q--)
    {
        int op, t, w;
        cin >> op >> t;
        if(op == 1)
        {
            cin >> w;
            pq.push(w - t);
        }
        else
        {
            if(pq.empty())
                cout << "-1\n";
            else
            {
                cout << min(V, pq.top() + t) << "\n";
                pq.pop();
            }
        }
    }
    
    return 0;
}

E - Sum of Square of Sum

  • 预估难度:普及+/提高-
  • 标签:组合数学

题意

\(N\) 个球,分别编号为 \(1\)\(N\)。第 \(i\) 个球上写有一个整数 \(A_i\)

对于从这 \(N\) 个球中选出若干个球的一种方案,定义该方案的得分为:选出的所有球的数字之和的平方。

请计算从 \(N\) 个球中选出 \(K\) 个球的所有 \(\binom{N}{K}\) 种方案的得分之和,结果对 \(998244353\) 取模。

数据范围

  • \(1 \leq K \leq N \leq 2\times 10^5\)
  • \(1 \leq A_i \leq 10^9\)

思路

\(K=1\) 时,答案即 \(\sum\limits_{i=1}^N A_i^2\)

\(K\gt 1\) 时,记选出的数为 \(B_1, B_2, \ldots, B_K\),那么得分即:

\[\begin{aligned} &(\sum\limits_{i=1}^K B_i)^2 \\ =&\sum\limits_{i=1}^K\sum\limits_{j=1}^K B_iB_j \\ =&\sum\limits_{i=1}^K B_i^2 + 2\sum\limits_{i=1}^K\sum\limits_{j=i+1}^K B_iB_j \end{aligned} \]

单独考虑左右两部分贡献。

  • 对于左半部分,单独考虑每个元素 \(A_i\) 的贡献:
    • 如果 \(A_i\) 被选中,对应的方案数共 \(\binom{N-1}{K-1}\) 种。
    • 此时 \(A_i\) 对左半部分产生的贡献为 \(A_i^2\),那么所有 \(A_i\) 的总贡献即 \(\sum\limits_{i=1}^N A_i^2\)
    • 可得整个左半部分对最终答案的贡献为 \(\binom{N-1}{K-1} \sum\limits_{i=1}^N A_i^2\)
  • 对于右半部分,考虑前后两个元素 \(A_i, A_j\) \((i \lt j)\) 的贡献:
    • 如果 \(A_i, A_j\) 两数被同时选中,对应的方案数共 \(\binom{N-2}{K-2}\) 种。
    • 此时元素对 \(A_i, A_j\) \((i \lt j)\) 对右半部分产生的贡献为 \(A_iA_j\),那么所有元素对的总贡献即 \(\sum\limits_{i=1}^K\sum\limits_{j=i+1}^K A_iA_j = \dfrac{(\sum\limits_{i=1}^N A_i)^2 - \sum\limits_{i=1}^N A_i^2}{2}\)
    • 可得整个右半部分对最终答案的贡献为 \(\binom{N-2}{K-2} \left ( (\sum\limits_{i=1}^N A_i)^2 - \sum\limits_{i=1}^N A_i^2 \right )\)

因此答案为:

\[\binom{N-1}{K-1} \sum\limits_{i=1}^N A_i^2 + \binom{N-2}{K-2} \left ( (\sum\limits_{i=1}^N A_i)^2 - \sum\limits_{i=1}^N A_i^2 \right ) \]

时间复杂度 \(O(N + \log N)\)

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const ll mod = 998244353;

int n, k;
ll a[200005];

ll fac[200005], inv[200005];

ll qpow(ll a, ll n)
{
    ll r = 1;
    while(n)
    {
        if(n & 1)
            r = r * a % mod;
        a = a * a % mod;
        n >>= 1;
    }
    return r;
}

void init(int n)
{
    fac[0] = 1;
    for(int i = 1; i <= n; i++)
        fac[i] = fac[i - 1] * i % mod;
    inv[n] = qpow(fac[n], mod - 2);
    for(int i = n - 1; i >= 0; i--)
        inv[i] = inv[i + 1] * (i + 1) % mod;
}

ll getC(int n, int m)
{
    if(n < m)
        return 0;
    return fac[n] * inv[m] % mod * inv[n - m] % mod;
}

int main()
{
    cin >> n >> k;
    
    init(n);
    
    ll sum = 0; // 总和
    ll sum2 = 0; // 平方和
    
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        sum = (sum + a[i]) % mod;
        sum2 = (sum2 + a[i] * a[i]) % mod;
    }
    
    if(k == 1)
        cout << sum2 << "\n";
    else
    {
        ll ans = getC(n-1, k-1) * sum2 % mod;
        ans = (ans + getC(n-2, k-2) * ((sum * sum % mod - sum2 + mod) % mod)) % mod;
        cout << ans << "\n";
    }
    
    return 0;
}

F - Concat (maximize)

  • 预估难度:提高
  • 标签:贪心、排序

题意

给定 \(N\) 个仅由数字 0~9 组成的字符串 \(S_1, S_2, \ldots, S_N\),每个字符串长度不超过 \(10\),可能开头为 \(0\)

请从中选出 \(K\) 个字符串,按任意顺序前后拼接成一个大字符串。

你需要保证得到的这个大字符串,如果解释为十进制整数,结果应当是最大的。

求最大答案是多少。

数据范围

  • \(1 \le K \le N \le 10^5\)

思路

如果已知选出的 \(K\) 个字符串分别为 \(A_1, A_2, \ldots, A_K\),这是一个贪心策略的经典问题,根据相邻两字符串拼接后的大小关系进行排序即可。

而我们今天则需要先从 \(N\) 个字符串中选出 \(K\) 个,再进行以上处理。

考虑要选择的第一个字符串 \(A_1\),不论接下来选择的 \(N-1\) 个字符串所组成的大字符串 \(T\) 具体如何,得到的最终答案均可以记作 \(S_x \times 10^{|T|} + T\)

对于这个最终答案,明显应当优先扩大 \(10^{|T|}\),也就是保证后面的 \(N-1\) 个字符串总长度最大。

那么我们便可以按照 \((\text{长度},\text{数值})\) 二元组对每个字符串 \(S_i\) 进行从大到小排序,然后讨论第一个字符串如何选择。

  • 如果选择的第一个字符串在 \(S_{1 \sim K}\) 内,由于此时已按照长度排序,长度相同时按数值排序,因此组成最终答案的字符串一定就是 \(S_1, S_2, \ldots, S_K\) 这些字符串本身。
  • 如果选择的第一个字符串在 \(S_{K \sim N}\) 内,同理组成最终答案的剩余 \(N-1\) 个字符串一定是 \(S_1, S_2, \ldots, S_{K-1}\) 这些字符串。根据前面的公式 \(S_x \times 10^{|T|} + T\),此时 \(T\) 由于已经确定,因此第一个字符串应当直接选择 \(S_{K \sim N}\) 中的数值最大的字符串。

处理出以上两种方案对应的两个最终答案后,最后处理下前导零,再判断哪个答案更优即可。

时间复杂度 \(O(N|L|\log N)\)

代码

#include<bits/stdc++.h>
using namespace std;

int n, k;

struct node
{
    int len;
    long long val;
    string s;
    bool operator < (const node &nd) const
    {
        if(len != nd.len)
            return len > nd.len;
        return val > nd.val;
    }
};
node a[100005];

bool cmp(string &a, string &b)
{
    return a + b > b + a;
}

string trim(string s)
{
    int p = 0;
    while(p < s.size() && s[p] == '0')
        p++;
    if(p == s.size()) // 整个字符串全是 0
        return "0";
    return s.substr(p); // 去除前导 0
}

int main()
{
    cin >> n >> k;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i].s;
        a[i].len = a[i].s.size();
        a[i].val = stoll(a[i].s);
    }
    sort(a + 1, a + n + 1);
    
    vector<string> v1, v2;
    for(int i = 1; i <= k; i++)
    {
        v1.push_back(a[i].s);
        if(i != k)
            v2.push_back(a[i].s);
    }
    sort(v1.begin(), v1.end(), cmp);
    sort(v2.begin(), v2.end(), cmp);
    
    long long mx = 0;
    for(int i = k; i <= n; i++)
        mx = max(mx, a[i].val);
    
    string ans1 = "", ans2 = to_string(mx);
    
    for(string &s : v1)
        ans1 += s;
    for(string &s : v2)
        ans2 += s;
    
    ans1 = trim(ans1);
    ans2 = trim(ans2);
    if(ans1.size() > ans2.size() || ans1.size() == ans2.size() && ans1 > ans2)
        cout << ans1;
    else
        cout << ans2;
    
    return 0;
}
posted @ 2026-08-15 22:09  StelaYuri  阅读(62)  评论(0)    收藏  举报