AtCoder Beginner Contest 480 ABCDE

A - ADjacent

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

题面

问题陈述

有一个 \(2\) 行 \(2\) 列的网格。

网格的每个格子中恰好写有 A、B、C、D 中的一个字符,且这 \(4\) 个格子中写出的字符互不相同。从上往下第 \(i\) 行、从左往右第 \(j\) 列的格子中写有 \(S_{i,j}\)。

如果写有 A 的格子与写有 D 的格子相邻(即它们共用一条边),则输出 Yes;否则输出 No。

约束

  • \(S_{i,j}\) 是 A、B、C、D 中的一个。
  • 若 \((i_1,j_1)\neq (i_2,j_2)\),则 \(S_{i_1,j_1}\) 与 \(S_{i_2,j_2}\) 不同。

输入

输入从标准输入按以下格式给出:

\(S_{1,1}S_{1,2}\)

\(S_{2,1}S_{2,2}\)

输出

在一行中输出答案。

思路

直接判断可行,只是条件需要多写一些。

或者可以用 \((x_A, y_A), (x_D, y_D)\) 四个变量记录字符 A 和 D 所在行和列,那么在 \(2 \times 2\) 的网格中,“相邻”也就代表着两个字符在同一行或是同一列,也就是判断 \(x_A = x_D\) 或 \(y_A = y_D\)。

代码

void solve()
{
    int ax, ay, dx, dy;
    for(int i = 1; i <= 2; i++)
        for(int j = 1; j <= 2; j++)
        {
            char c;
            cin >> c;
            if(c == 'A')
                ax = i, ay = j;
            if(c == 'D')
                dx = i, dy = j;
        }
    
    if(ax == dx || ay == dy) // 同一行或者同一列
        cout << "Yes";
    else
        cout << "No";
}

B - Invalid Vote

  • 预估难度:普及-
  • 标签:字符串、模拟

题面

问题陈述

\(N\) 名候选人 \(1,2,\dots,N\) 正在参加一场选举。每名候选人的名字均由小写英文字母组成,且互不相同。候选人 \(i\) 的名字为 \(S_i\)。

共有 \(M\) 张选票 \(1,2,\dots,M\),第 \(j\) 张选票上写有一个由小写英文字母组成的字符串 \(T_j\)。

若存在名字与 \(T_j\) 完全相同的候选人,则该候选人获得 \(1\) 票。否则,若存在恰好一名候选人的名字可以通过改动 \(T_j\) 中的 \(1\) 个字符得到,则该候选人获得 \(1\) 票。否则,这张选票作废。

对于每个 \(i=1,2,\dots,N\),求候选人 \(i\) 获得的票数。

约束

  • \(1 \leq N \leq 100\)
  • \(1 \leq M \leq 100\)
  • \(1 \leq |S_i| \leq 10\)
  • 若 \(i \neq j\),则 \(S_i \neq S_j\)。
  • \(1 \leq |T_j| \leq 10\)
  • \(S_i\) 与 \(T_j\) 均为由小写英文字母组成的字符串。
  • \(N\) 与 \(M\) 为整数。

输入

输入从标准输入按以下格式给出:

\(N\) \(M\)

\(S_1\)

\(S_2\)

\(\vdots\)

\(S_N\)

\(T_1\)

\(T_2\)

\(\vdots\)

\(T_M\)

输出

共输出 \(N\) 行。
第 \(i\) 行应包含候选人 \(i\) 获得的票数。

思路

数据范围很小,因此直接按题意暴力模拟即可:对每张选票,把所有候选人的名字都扫一遍,判断它和选票之间的距离。

定义函数 check(s, name) 返回把选票字符串 \(s\) 改成候选人名字所需修改的字符数,即两串的逐位不同字符个数。若两者长度不同,则无论改几个字符都不可能变成对方,直接返回一个无效值(如 \(-1\))排除掉。

对一张选票,遍历所有候选人:

  • 若 check 返回 \(0\),说明名字与选票完全相同,按题意这位候选人直接得票,可以立刻结束对这张选票的处理;
  • 若返回 \(1\),说明恰好只差一个字符,先用 id 记下这位候选人的下标,同时 cnt 累加这样的候选人数;
  • 其余情况(长度不同或差异超过 \(1\) 个字符)均不考虑。

遍历结束后,只有当 cnt == 1 时才给 id 对应的候选人加上一票;cnt == 0 表示没有任何候选人能被改成选票,cnt >= 2 表示有多个候选人满足条件,这两种情况都按题意作废。

时间复杂度 \(O(NML)\),其中 \(L\) 表示字符串长度。

代码

int n, m;
string name[105];
int score[105]; // score[i] -> name[i] 获得的选票数

// 求将 a 变成 b 需要修改的字符数量
int check(string a, string b)
{
    if(a == b)
        return 0;
    if(a.size() != b.size())
        return -1;
    int cnt = 0;
    for(int i = 0; i < a.size(); i++)
        cnt += (a[i] != b[i]);
    return cnt;
}

// 处理名字为 s 的选票
void vote(string s)
{
    int id, cnt = 0;
    // 如果存在仅修改一个字符就能得到的名字
    // 用 id 记录该名字对应的下标,cnt 记录有多少个这样的名字
    for(int i = 1; i <= n; i++)
    {
        int r = check(s, name[i]);
        if(r == 0) // 就是对应名字
        {
            score[i]++;
            return;
        }
        if(r == 1) // 仅需修改一次
        {
            id = i;
            cnt++;
        }
    }
    if(cnt == 1) // 只有一个名字能通过修改一个字符得到
        score[id]++;
}

void solve()
{
    cin >> n >> m;
    
    for(int i = 1; i <= n; i++)
        cin >> name[i];
    
    for(int i = 1; i <= m; i++)
    {
        string s;
        cin >> s;
        vote(s);
    }
    
    for(int i = 1; i <= n; i++)
        cout << score[i] << "\n";
}

C - Self-Stopping Shower

  • 预估难度:普及-
  • 标签:排序、数学、贪心

题面

问题陈述

高桥正在公共浴场使用淋浴。这个淋浴有一个按钮。若在时刻 \(t\) 按下按钮,则从时刻 \(t\) 到时刻 \(t+K\) 的 \(K\) 秒内会持续出热水。即使在出热水期间再次按下按钮,也会从最后一次按下按钮的时刻起再持续出 \(K\) 秒热水。

有 \(N\) 个青木,每个青木会各按一次按钮。第 \(i\) 个青木在时刻 \(A_i\) 按下按钮。

求为了使热水从时刻 \(0\) 到时刻 \(T\) 始终处于流出状态,高桥至少需要按下按钮多少次。这里,按下按钮的瞬间以及最后一次按下按钮后恰好 \(K\) 秒的瞬间,均视为正在出热水。

约束

  • \(1 \leq N \leq 2 \times 10^5\)
  • \(1 \leq K \leq 10^9\)
  • \(1 \leq T \leq 10^9\)
  • \(0 \leq A_i \leq 10^9\)
  • 输入的所有值均为整数。

输入

输入从标准输入按以下格式给出:

\(N\) \(K\) \(T\)

\(A_1\) \(A_2\) \(\ldots\) \(A_N\)

输出

在一行中输出答案。

思路

每个青木按按钮都可以看成一段长度为 \(K\) 的热水区间 \([A_i, A_i+K]\)(两端都算正在出热水)。我们要做的就是让这些区间并起来能够完整覆盖 \([0,T]\),而高桥自己按按钮,则是用来填补区间之间出现的"断水空隙"。

先把所有 \(A_i\) 排序。排序后相邻区间一旦有重叠部分(\(A_i + K \ge A_{i+1}\)),中间就不会断水,可以直接跳过;而若 \(A_i + K \lt A_{i+1}\),说明中间有一段长度为 \(A_{i+1} - A_i - K\) 的空隙会停水,这就是需要额外按按钮的部分。

对于一段长度为 delta 的没有水的空隙而言,贪心可得,我们应当从空隙的起始时间开始,每隔恰好 \(K\) 秒按一次按钮,才能够使得额外按按钮的次数尽可能少。因此需要额外按按钮的次数可以通过 \(\lceil\dfrac{\text{delta}}{K}\rceil\) 计算获得。

最后单独考虑两段:

  • 开头 \(0 \sim A_1\):第一个青木按下之前没有任何热水,因此会产生 \(A_1\) 秒的间隙。
  • 结尾:最后一次有效按键之后到 \(T\) 的这部分间隙。

对于结尾部分需要注意,有可能某些按钮按下的时刻大于等于 \(T\),明显这部分按钮对答案是没有帮助的,因此可以直接舍弃。然后为了计算方便,我们可以假设在时刻 \(T\) 有一个虚拟的青木按了一次按钮(实际有没有均不影响答案),这样最后这一段也就可以当成"两个相邻按键之间的空隙",可以直接沿用前面的做法,不需要额外特判。

最后,把所有空隙的 \(\lceil\dfrac{\text{delta}}{K}\rceil\) 累加即为答案。时间复杂度 \(O(N\log N)\)。

代码

int n, k, t;
int a[200005];

// 如果有 delta 秒没有热水用,计算需要额外按多少次
// 即 delta / k 向上取整
int cal(int delta)
{
    return (delta + k - 1) / k;
}

void solve()
{
    cin >> n >> k >> t;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a + 1, a + n + 1);
    
    // 可能某几次按按钮的时间 > t,但无需考虑
    while(n >= 1 && a[n] >= t)
        n--;
    a[n + 1] = t; // 假设第 t 秒也按了按钮,方便计算最后面的间隔
    
    int cnt = 0; // 需要额外按按钮的次数
    cnt += cal(a[1]); // 0 ~ a[1] 的间隙
    for(int i = 1; i <= n; i++)
    {
        // 第 i 次按按钮后,出热水的时间能持续到下一次按按钮,则无需额外按
        if(a[i] + k >= a[i + 1])
            continue;
        // 否则,两次按按钮的间隔时间为 a[i+1] - a[i]
        // 除了持续出热水的 k 秒以外,剩余时间就是没有热水的间隙
        cnt += cal(a[i + 1] - a[i] - k);
    }
    cout << cnt;
}

D - Concatenation of Prefixes

  • 预估难度:普及
  • 标签:前缀和、二分、数学

题面

问题陈述

给定一个长度为 \(N\) 的整数序列 \((A_1,\dots,A_N)\)。

按以下步骤构造一个长度为 \(\frac{N(N+1)}{2}\) 的序列 \(B\)。

  • 初始时,令 \(B\) 为空序列。
  • 按 \(i=1,2,\dots,N\) 的顺序,依次将 \((A_1,\dots,A_i)\) 追加到 \(B\) 的末尾。

给定 \(Q\) 个以下形式的询问,请回答它们。

询问:给定 \(L\) 和 \(R\)。求 \(B_L+B_{L+1}+\dots+B_R\)。

约束

  • \(1 \leq N \leq 10^5\)
  • \(1 \leq Q \leq 10^5\)
  • \(-10^8 \leq A_i \leq 10^8\)
  • 对于每个询问,\(1\leq L\leq R \leq \frac{N(N+1)}{2}\)。
  • 输入的所有值均为整数。

输入

输入从标准输入按以下格式给出:

\(N\) \(Q\)

\(A_1\) \(A_2\) \(\dots\) \(A_N\)

\(\mathrm{query}_1\)

\(\mathrm{query}_2\)

\(\vdots\)

\(\mathrm{query}_Q\)

这里,\(\mathrm{query}_i\) 表示第 \(i\) 个询问,按以下格式给出:

\(L\) \(R\)

输出

输出 \(Q\) 行。
第 \(i\) 行应包含对第 \(i\) 个询问的答案。

思路

我们记一个过程序列 \(C = (C_1, C_2, \ldots, C_N)\),其中 \(C_i = (A_1, A_2, \ldots, A_i)\)。那么题目中 \(B\) 序列的构造过程就可以描述成:把 \(C_1, C_2, \ldots, C_N\) 按顺序进行前后拼接。

由于 \(C_i\) 的长度为 \(i\),因此前 \(t\) 个 \(C_i\) 的总长度也就是 \(1+2+\ldots + t = \dfrac{(1+t)\cdot t}{2}\)。

对于每个给定的问题 \(\sum\limits_{i=L}^{R} B_i\),借助前缀和思想将其拆成 \(\sum\limits_{i=1}^R B_i - \sum\limits_{i=1}^{L-1} B_i\),那么只要能快速求出 \(B\) 序列的前缀和,就能直接得出问题的答案。

若想求出 \(B\) 序列的前 \(m\) 项和,我们可以先求出对应的最大项 \(B_m\) 来源于 \(C\) 中的哪个过程序列,也就是先找到一个整数 \(t\),满足:

\[\dfrac{(1+t)\cdot t}{2} \lt m \le \dfrac{[1 + (t+1)]\cdot (t+1)}{2} \]

那么 \(B\) 序列的前 \(m\) 项和便可以看作是由以下两部分相加组成:

  • \(C_1, C_2, \ldots, C_t\) 这些过程序列的总和;
  • \(C_{t+1}\) 这个序列的前 \(m - \dfrac{(1+t)\cdot t}{2}\) 项和,也就是原本 \(A\) 序列的前 \(m - \dfrac{(1+t)\cdot t}{2}\) 项和。

对于以上第二部分,只需要开一个前缀和数组 s[] 记录 \(A\) 序列的前缀和,即可快速求出。

而对于以上第一部分,由于 \(C_i\) 的总和也就是 \(A\) 序列的前 \(i\) 项和,即 s[i],那么只需要再对 s[] 做二维前缀和记作 p[],即可借助 p[t] 快速求出。

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

代码

typedef long long ll;

int n, q, a[100005];
ll s[100005]; // s[i] 记 a[] 的前缀和
ll p[100005]; // p[i] 记 s[] 的前缀和

// 求前 m 项的总和
ll query(ll m)
{
    // 二分求最大的 t 满足 1+2+...+t <= m
    ll l = 1, r = n, t = 0;
    while(l <= r)
    {
        ll mid = (l + r) / 2;
        if((1 + mid) * mid / 2 <= m)
        {
            t = mid;
            l = mid + 1;
        }
        else
            r = mid - 1;
    }
    return p[t] + s[m - (1 + t) * t / 2];
}

void solve()
{
    cin >> n >> q;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        s[i] = s[i - 1] + a[i];
        p[i] = p[i - 1] + s[i];
    }
    while(q--)
    {
        ll l, r;
        cin >> l >> r;
        cout << query(r) - query(l - 1) << "\n";
    }
}

E - Product-Bounded Arithmetic Sequences

  • 预估难度:普及+/提高-
  • 标签:枚举、数学、二分

题面

问题陈述

若一个正整数序列 \(A=(A_1,\dots,A_{|A|})\) 满足以下全部条件,则称其为好序列。

  • \(|A|\geq 3\)
  • \(A\) 是等差数列;即对所有 \(1 \leq i \leq |A|-1\) 都有 \(A_{i+1}-A_{i}=A_2-A_1\)。
  • \(A_2-A_1 > 0\)

求所有元素之积不超过 \(N\) 的好序列的个数。

可以证明,在给定约束下答案不超过 \(10^{18}\)。

约束

  • \(1 \leq N \leq 10^{18}\)
  • \(N\) 为整数。

输入

输入从标准输入按以下格式给出:

\(N\)

输出

输出答案。

思路

可以发现合法序列仅由 首项 \(a\)、公差 \(d\)、项数 \(k\) 决定,且根据题意可得 \(a \ge 1, d \ge 1, k \ge 3\)。那么我们只需要统计有多少三元组 \((a, d, k)\) 满足下式即可:

\[\prod_{i=0}^{k-1} (a + i\cdot d) \le N \]

考虑先确定 \(k\) 的范围。可以发现当 \(a=d=1\) 时 \(k\) 能取得最大值,但此时等差序列的乘积就是 \(k!\),因此 \(k\) 的最大值满足 \(k! \le N \le 10^{18}\),即 \(k \le 19\),范围很小,可以枚举。

然后考虑剩下的两项 \(a\) 和 \(d\) 要再枚举谁:

  • 对于 \(a\),由于 \(a^k \le \prod\limits_{i=0}^{k-1} (a + i\cdot d) \le N\),根据 \(a^k \le N\) 可得 \(a \le \sqrt[k]{N}\),发现在 \(k=3\) 时取到最大值 \(N^{\frac 1 3} \le 10^6\),且随着 \(k\) 变大而迅速降低。如果枚举 \(a\),枚举次数将会是 \(\sum\limits_k \sqrt[k]{N}\)。
  • 对于 \(d\),在 \(a=1, k=3\) 时,根据 \((1+d)(1+d^2) \le N\) 可得 \(d \approx 7 \times 10^8\)。

因此接下来再考虑枚举 \(a\),然后在 \(k, a\) 确定后,二分求出最大的项数 \(d_{\text{max}}\),即可得出目前对应的三元组数量。

至于枚举和二分过程中的合法性检查,可以将当前要判断的首项 \(a\)、公差 \(d\)、项数 \(k\) 传递给一个函数,然后模拟等差数列顺序相乘,过程中判断是否超过 \(N\) 的范围即可,时间复杂度 \(O(k)\),注意乘法可能超出 long long 范围,推荐使用 __int128,且在乘法过程中马上对范围进行判断。

时间复杂度 \(O(\sum\limits_{k} \sqrt[k]{N} \log N)\),由 \(k = 3\) 主导,即 \(O(\sqrt[3]{N}\log N)\)。

代码

typedef long long ll;

ll N;

// 检查首项为 a,公差为 d,项数为 k 的等差数列 乘积是否 <= N
bool check(ll a, ll d, int k)
{
    __int128 m = 1;
    for(int i = 0; i < k; i++)
    {
        m *= a + i * d;
        if(m > N)
            return false;
    }
    return true;
}

void solve()
{
    cin >> N;
    
    ll ans = 0;
    for(int k = 3; check(1, 1, k); k++) // 枚举项数
        for(int a = 1; check(a, 1, k); a++) // 枚举首项
        {
            // 二分找最大公差 d
            ll l = 1, r = 1e9, d = 0;
            while(l <= r)
            {
                ll mid = (l + r) / 2;
                if(check(a, mid, k))
                {
                    d = mid;
                    l = mid + 1;
                }
                else
                    r = mid - 1;
            }
            // 1 ~ d 均可作为公差使用
            ans += d;
        }
    cout << ans;
}
posted @ 2026-10-17 21:40  StelaYuri  阅读(35)  评论(0)    收藏  举报