AtCoder Beginner Contest 477 ABCDE

A - Traffic Light

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

题面

问题描述

某个交通信号灯的颜色按 B(蓝色)、Y(黄色)、R(红色)、B(蓝色)、Y(黄色)、R(红色)、\(\dots\) 的顺序周期性变化。

给定一个字符 \(c\),表示交通信号灯当前的颜色。输出交通信号灯的下一个颜色。

约束条件

  • \(c\) 是 B、Y、R 之一。

输入

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

\(c\)

输出

输出表示交通信号灯下一个颜色的字符。

代码

void solve()
{
    char c;
    cin >> c;
    if(c == 'B')
        cout << 'Y';
    else if(c == 'Y')
        cout << 'R';
    else
        cout << 'B';
}

B - Standing Outliers

  • 预估难度:入门
  • 标签:暴力枚举、一维数组

题面

问题陈述

数轴上有 \(N\) 个人。这些人编号为 \(1\) 到 \(N\),第 \(i\) 个人位于坐标 \(X_i\)。多个人可能位于同一坐标。

如果某人与其他每个人的距离都至少为 \(D\),则称此人“离群”。

形式化地,如果对于所有 \(j \ (1 \leq j \leq N,\ j \neq i)\) 都有 \(|X_i-X_j| \geq D\),则称第 \(i\) 个人“离群”。

按升序输出所有“离群”的人的编号。

约束条件

  • \(2 \le N \le 100\)
  • \(1 \le D \le 10^9\)
  • \(1 \le X_i \le 10^9\)
  • 所有输入值均为整数。

输入

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

\(N\) \(D\)
\(X_1\) \(X_2\) \(\dots\) \(X_N\)

输出

设 \(K\) 为“离群”的人数,并设 \((P_1,P_2,\dots,P_K)\) 为他们的编号按升序排列得到的序列。按以下格式输出它们:

\(K\)
\(P_1\) \(P_2\) \(\dots\) \(P_K\)

如果 \(K=0\),第二行应为空。

代码

void solve()
{
    int n, d, x[105];
    cin >> n >> d;
    
    for(int i = 1; i <= n; i++)
        cin >> x[i];
    
    vector<int> ans;
    
    for(int i = 1; i <= n; i++) // 枚举每个人
    {
        bool flag = false; // 标记是否有人与 i 距离 < d
        for(int j = 1; j <= n; j++) // 枚举其他人
        {
            if(j == i)
                continue;
            if(abs(x[j] - x[i]) < d) // 存在一个人与 i 的距离 < d
            {
                flag = true;
                break;
            }
        }
        if(!flag)
            ans.push_back(i);
    }
    
    cout << ans.size() << "\n";
    for(int i : ans)
        cout << i << " ";
}

C - Range Search Query

  • 预估难度:普及-
  • 标签:字符串、前缀和

题面

问题陈述

给定字符串 \(S\) 和 \(T\)。
处理 \(Q\) 个询问。
每个询问给出 \(L\) 和 \(R\)。判断由 \(S\) 的第 \(L\) 个到第 \(R\) 个字符组成的子串是否包含 \(T\) 作为子串。

约束

  • \(S\) 是长度在 \(1\) 到 \(4 \times 10^5\) 之间的字符串,由小写英文字母组成。
  • \(T\) 是长度在 \(1\) 到 \(10\) 之间的字符串,由小写英文字母组成。
  • \(1 \leq Q \leq 2 \times 10^5\)
  • \(1 \leq L \leq R \leq |S|\)
  • \(Q, L, R\) 是整数。

输入

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

\(Q\)
\(S\)
\(T\)
\(\mathrm{query}_1\)
\(\mathrm{query}_2\)
\(\vdots\)
\(\mathrm{query}_Q\)

每个询问 \(\mathrm{query} _ q\) 按以下格式给出:

\(L\) \(R\)

输出

输出 \(Q\) 行。对于第 \(q\) 个询问,如果由 \(S\) 的第 \(L\) 个到第 \(R\) 个字符组成的子串包含 \(T\) 作为子串,则第 \(q\) 行应输出 Yes,否则输出 No。

思路

考虑到 \(T\) 字符串固定不变,且长度较短(最大仅为 \(10\)),考虑预处理出字符串 \(S\) 中有哪些位置的子串与 \(T\) 完全对应即可,时间复杂度 \(O(|S|\cdot |T|)\)。

记 \(\text{ok}_i\) 表示以 \(i\) 为左端点时,子串 \(S_{[i, i+|T|-1]}\) 与字符串 \(T\) 是否相等,值取 true / false。

对于每次询问 \([L, R]\),考虑边界情况:

  • 如果子串左端点取 \(L\),对应的匹配结果为 \(\text{ok}_L\)。
  • 如果子串右端点取 \(R\),对应左端点即 \(R-|T|+1\),对应的匹配结果即 \(\text{ok}_{R-|T|+1}\)。

因此单次询问只需要在下标区间 \([L, R-|T|+1]\) 范围内,检查是否存在某个位置的 \(\text{ok}_i\) 为 true 即可。静态区间查询,可以借助前缀和思想优化为 \(O(1)\)。

总时间复杂度 \(O(|S|\cdot |T| + Q)\)。

代码

int pre[400005];
// 假设字符串下标从 1 开始
// pre[i] 表示在 1~i 范围内有多少个位置 x 可以作为起点
// 使得 s[x ... x+|t|-1] 与 t 相等

void solve()
{
    int q;
    cin >> q;
    
    string s, t;
    cin >> s >> t;
    
    for(int i = 0; i + t.size() - 1 < s.size(); i++) // 枚举可行的 s[] 子串起点
    {
        pre[i + 1] = pre[i]; // 前缀和,下标从 1 开始
        if(s.substr(i, t.size()) == t) // 判断 s[i ... i+|t|-1] 与 t 是否匹配
            pre[i + 1]++;
    }
    
    while(q--)
    {
        int l, r;
        cin >> l >> r;
        if(r - l + 1 < t.size()) // 长度不够,不可能出现子串 t
            cout << "No\n";
        else
        {
            // 当长度为 |t| 的子串右端点为 r 时,对应左端点为 r-|t|+1
            // 判断在 l ~ r-|t|+1 范围内是否存在某个位置 可以作为起点,使子串与 t 相等
            if(pre[r - t.size() + 1] - pre[l - 1] > 0)
                cout << "Yes\n";
            else
                cout << "No\n";
        }
    }
}

D - Masking Tape

  • 预估难度:普及
  • 标签:模拟、计数思想

题面

问题陈述

有 \(N\) 个方格排成一行,从左到右编号为方格 \(1\) 到方格 \(N\)。颜色用小写英文字母表示。

初始时,每个方格的颜色都是 a,且任何方格上都没有放置瓷砖。

给定 \(Q\) 个查询;请按给定顺序处理它们。每个查询为以下两种类型之一:

  • 1 X:如果方格 \(X\) 上没有放置瓷砖,则在其上放置一块瓷砖;如果其上已放置瓷砖,则移除该瓷砖。

  • 2 C:将所有未放置瓷砖的方格的颜色改为 \(C\)。

求处理完所有查询后每个方格的颜色(无论其上是否放置有瓷砖)。

约束条件

  • \(1 \le N,Q \le 3\times 10^5\)
  • 每个查询都是类型 \(1\) 或类型 \(2\)。
  • 在类型 \(1\) 查询中,\(X\) 是满足 \(1 \le X \le N\) 的整数。
  • 在类型 \(2\) 查询中,\(C\) 是单个小写英文字母。

输入

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

\(N\) \(Q\)
\(\mathrm{query}_1\)
\(\vdots\)
\(\mathrm{query}_Q\)

每个查询 \(\mathrm{query} _ i \ (1 \le i \le Q)\) 按以下形式给出:

\(1\) \(X\)

或

\(2\) \(C\)

输出

输出各方格的最终颜色,作为一个长度为 \(N\) 的字符串,从左到右拼接。

思路

下文的“全局颜色”指代上一次操作 2 为所有未放置瓷砖的方格所涂的颜色,初始为 a。

如果正向模拟每次类型 2 操作都要把 \(O(N)\) 个格子刷一遍颜色,直接做是 \(O(NQ)\),肯定不行,需要把"整段染色"这个操作想办法离线削减掉。

注意到某个方格最终的颜色,只取决于它最后一次被瓷砖覆盖的那一段时间里,全局颜色是什么。因为一旦瓷砖被取走、且之后又执行了类型 2,它就会被重新刷成新的全局颜色;反之如果取走后没有再出现类型 2,它保留的就是最后一次覆盖时被定住的那个颜色。

因此完全没必要真的去刷颜色,只需要维护两个"全局"变量:

  • T 记录最后一次类型 2 操作发生的时间(用操作编号表示);
  • C 记录最后一次类型 2 操作设置的颜色。

再为每个方格开三个数组:

  • cover[i]:第 \(i\) 个格子当前是否放有瓷砖;
  • color[i]:第 \(i\) 个格子最后一次被放置瓷砖时的全局颜色;
  • tim[i]:第 \(i\) 个格子最后一次被取消瓷砖覆盖的时间(若从未被覆盖过则为 \(0\))。

处理类型 1 时按题意翻转 cover[x]:放置瓷砖时顺手把当前全局颜色 C 记进 color[x];取消时则把当前操作编号 \(i\) 记进 tim[x]。

但要注意一个细节:放置瓷砖的那一刻格子颜色未必是 C。如果这个格子在此前被覆盖过、又已经取消,且取消之后没有再出现过类型 2 操作(即 tim[x] != 0 && T <= tim[x]),说明它的颜色从上次记录起就没变过,此时再去记 C 反而是错的。代码里用 tim[x] == 0 || T > tim[x] 正是为了排除这种情况——只有从未被覆盖过,或取消覆盖之后又发生过类型 2,才需要更新 color[x]。

最后输出时逐个方格判断:

  • cover[i] 为真:瓷砖还压着,直接输出 color[i];
  • 否则看是否在取消覆盖之后发生过类型 2(tim[i] == 0 表示从未覆盖过,也可视作成立):若 tim[i] == 0 或 tim[i] < T,说明它现在等于全局颜色,输出 C;
  • 剩余情况说明取消覆盖后颜色没再被全局改过,输出 color[i]。

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

代码

int T = 0;
char C = 'a';
// T 记录 最后一次执行操作 2 的时间(按操作编号)
// C 记录 最后一次执行操作 2 时设置的颜色

bool cover[300005];
char color[300005];
int tim[300005];
// cover[i] 记录第 i 个方格 目前是否放置了瓷砖
// color[i] 记录第 i 个方格 最后一次 放置瓷砖 时的方格颜色
// tim[i] 记录第 i 个方格 最后一次 取消放置瓷砖 的时间

void solve()
{
    int n, q;
    cin >> n >> q;
    for(int i = 1; i <= q; i++)
    {
        int op;
        cin >> op;
        if(op == 1)
        {
            int x;
            cin >> x;
            if(!cover[x])
            {
                cover[x] = true;
                if(tim[x] == 0 || tim[x] != 0 && T > tim[x])
                    color[x] = C; // 记录当前的全局颜色
            }
            else
            {
                cover[x] = false;
                tim[x] = i;
            }
        }
        else // 操作 2,更新 T 和 C 变量
        {
            cin >> C;
            T = i;
        }
    }
    
    for(int i = 1; i <= n; i++)
    {
        if(cover[i]) // 如果有瓷砖覆盖,则直接输出最后一次覆盖时记录的颜色
            cout << color[i];
        else
        {
            if(tim[i] == 0) // 如果从来没有覆盖过瓷砖,直接输出全局颜色
                cout << C;
            else if(tim[i] < T) // 如果覆盖过瓷砖,且取消覆盖后执行过操作 2,依然输出全局颜色
                cout << C;
            else // 如果覆盖过瓷砖,但取消覆盖后没有执行过操作 2,输出最后一次覆盖时记录的颜色
                cout << color[i];
        }
    }
}

E - Wheel Distance

  • 预估难度:普及+/提高-
  • 标签:图论、最短路、前缀和

题面

题目描述

给定一个带权无向图,有 \(N+1\) 个顶点,编号为 \(1\) 到 \(N+1\)。
对于每个满足 \(1 \leq i \leq N\) 的整数 \(i\),存在一条权值为 \(A _ i\) 的边连接顶点 \(i\) 和顶点 \((i \bmod N) + 1\)。
另外,对于每个满足 \(1 \leq i \leq N\) 的整数 \(i\),存在一条权值为 \(B _ i\) 的边连接顶点 \(i\) 和顶点 \(N+1\)。不存在其他边。

处理 \(Q\) 个询问。在每个询问中,给定 \(S\) 和 \(T\);求从顶点 \(S\) 到顶点 \(T\) 的最短路径长度。

约束条件

  • \(3 \leq N \leq 2 \times 10^5\)
  • \(1 \leq Q \leq 2 \times 10^5\)
  • \(1 \leq A _ i, B _ i \leq 10^9\)
  • \(1 \leq S \lt T \leq N+1\)
  • 所有输入值均为整数。

输入

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

\(N\) \(Q\)
\(A_1\) \(A_2\) \(\dots\) \(A_N\)
\(B_1\) \(B_2\) \(\dots\) \(B_N\)
\(\mathrm{query}_1\)
\(\mathrm{query}_2\)
\(\vdots\)
\(\mathrm{query}_Q\)

每个询问 \(\mathrm{query} _ q\) 按以下格式给出:

\(S\) \(T\)

输出

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

思路

注意到整张图里只有 \(N+1\) 一个“特殊点”,任意一条从 \(S\) 到 \(T\) 的路径只可能有两种形态:

  • 完全不走环外的点 \(N+1\):此时路径被限制在环上,而从 \(S\) 到 \(T\) 只有两条环上的走法只有两种:顺着编号增大直接走过去,或者绕过 \(1\) 与 \(N\) 之间的那条边反着走。记 sa[i] 为 \(A_1 + \dots + A_i\),那么两种走法取最小值即可:
    • 正向走 \(S \to T\) 的距离是 sa[T-1] - sa[S-1]。
    • 反向绕行 \(S \to 1 \to N \to T\) 的距离则是 sa[S-1] + (sa[n] - sa[T-1])。
  • 中途经过 \(N+1\):也就是先从 \(S\) 走到 \(N+1\),再从 \(N+1\) 走到 \(T\)。由于最短路中途重复经过某个点一定不会更优,因此可以直接记 dis[i] 表示 \(N+1\) 到 \(i\) 的最短路,然后放心地把这条路径直接拆成 dis[S] + dis[T]。

sa 数组可以方便地借助前缀和求出,dis 数组则可以在 \(N+1\) 这个点上跑一遍单源最短路 Dijkstra 预处理得到。

最后注意一种特殊情况:\(T = N+1\),此时第二种形态已退化为 dis[S] 本身,直接输出即可。

预处理 Dijkstra 复杂度 \(O((N+Q)\log N)\),每个询问 \(O(1)\),总时间复杂度 \(O((N + Q)\log N)\)。

代码

typedef long long ll;
typedef pair<ll, int> pli;

struct edge
{
    int v, w;
};

int n, q, a[200005], b[200005];
ll sa[200005];
// sa[i] 记录 a[1 .. i] 的前缀和,表示从 1 到 i+1 的正向环上总长度

vector<edge> G[200005];

ll dis[200005];
// dis[i] 记录 n+1 -> i 的最短路长度

void dijkstra(int s)
{
    dis[s] = 0;
    priority_queue<pli> q;
    q.push(pli(0, s));
    while(!q.empty())
    {
        pli p = q.top();
        q.pop();
        int u = p.second;
        if(-p.first != dis[u])
            continue;
        for(edge e : G[u])
        {
            int v = e.v, w = e.w;
            if(dis[v] > dis[u] + w)
            {
                dis[v] = dis[u] + w;
                q.push(pli(-dis[v], v));
            }
        }
    }
}

void addedge(int u, int v, int w)
{
    G[u].push_back({v, w});
    G[v].push_back({u, w});
}

void solve()
{
    cin >> n >> q;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        sa[i] = sa[i - 1] + a[i];
        addedge(i, i % n + 1, a[i]);
    }
    for(int i = 1; i <= n; i++)
    {
        cin >> b[i];
        addedge(i, n + 1, b[i]);
    }
    
    memset(dis, 0x3f, sizeof dis);
    dijkstra(n + 1);
    
    while(q--)
    {
        int s, t;
        cin >> s >> t;
        
        if(t == n + 1)
        {
            cout << dis[s] << "\n";
            continue;
        }
        
        // s -> t 记 sa[t - 1] - sa[s - 1]
        ll x = sa[t - 1] - sa[s - 1];
        // t -> n -> 1 记 sa[n] - sa[t - 1]
        // 1 -> s      记 sa[s - 1]
        ll y = sa[s - 1] + (sa[n] - sa[t - 1]);
        
        cout << min(min(x, y), dis[s] + dis[t]) << "\n";
    }
}
posted @ 2026-09-26 21:43  StelaYuri  阅读(9)  评论(0)    收藏  举报