AtCoder Beginner Contest 436 ABCDEF 题目解析

A - o-padding

题意

给定一个整数 \(N\) 和一个长度小于 \(N\) 且由小写英文字母组成的字符串 \(S\)

请在 \(S\) 开头重复添加小写字母 o,直到其长度变为 \(N\)

思路

在输出 \(S\) 之前补上 \(N - |S|\)o 即可。

代码

void solve()
{
    int n;
    string s;
    cin >> n >> s;
    cout << string(n - s.size(), 'o') << s;
}

B - Magic Square

题意

给定一个至少是 \(3\) 的奇数 \(N\)

有一个 \(N \times N\) 的网格,网格中的所有单元格最初都是空的。记 \((i, j)\) \((0 \le i, j \lt N)\) 表示第 \(i+1\) 行第 \(j+1\) 列的单元格。

现在,你将按照下面的步骤在网格的每个单元格中写入整数。

  1. \((0,\frac{N-1}{2})\) 单元格中写入 \(1\)
  2. 重复以下操作 \(N^2-1\) 次:
    • 假设 \(k\) 是上一次写入的整数。对于写入 \(k\) 这个整数的单元格来说,如果该位置的右上角单元格为空,则在该位置写入整数 \(k+1\);否则,在该位置的下一行写入整数 \(k+1\)
      • 这里的“右上角”指“行 \(-1\),列 \(+1\)”后的位置。如果位于第一行,则行 \(-1\) 后会来到最后一行;如果位于最后一列,则列 \(+1\) 后会来到第一列。
      • 同理,“下一行”指“行 \(+1\)”后的位置。如果位于最后一行,则行 \(+1\) 后会来到第一行。

输出最终的整个网格。

思路

模拟题。

由于网格下标范围在 \([0, N-1]\) 范围内,因此减法可以通过加上一倍 \(N\) 后再除以 \(N\) 取余数,来保证不会出现负值。即 \((r - 1) \bmod N = (r - 1 + N) \bmod N\)

代码

int n, a[105][105];

void solve()
{
    cin >> n;
    
    int x = 0, y = (n - 1) / 2;
    a[x][y] = 1;
    
    for(int i = 2; i <= n * n; i++)
    {
        int xx = (x - 1 + n) % n; // 行 -1
        int yy = (y + 1) % n;     // 列 +1
        if(a[xx][yy] == 0) // 右上角为空
            x = xx, y = yy;
        else               // 否则只让行 +1
            x = (x + 1) % n;
        a[x][y] = i;
    }
    
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < n; j++)
            cout << a[i][j] << " ";
        cout << "\n";
    }
}

C - 2x2 Placing

题意

有一个 \(N \times N\) 的网格。令 \((i,j)\) 表示第 \(i\) 行第 \(j\) 列的单元格。起初,网格内没有任何内容。

你将执行 \(M\) 次操作,第 \(i\) 次操作如下:

  • 在网格上放置一个大小为 \(2 \times 2\) 且左上角为 \((R_i,C_i)\) 单元格的方块,前提是该方块的这四个位置不与其他已放置的图块重叠。如果存在某个位置重叠,则当前这个方块不放置。

在完成所有操作后,求网格上一共放置了多少个方块。

思路

网格很大,不能直接用计数数组标记哪些位置已经被使用过。

可以借助 set / map 等支持快速插入、快速查询的容器嵌套 pair 或自定义结构体来维护所有被使用的位置。

当有个方块以 \((x, y)\) 作为其左上角时,首先需要保证 \((x, y), (x+1,y), (x, y+1), (x+1, y+1)\) 四个网格均未被占用,才能够放置。

※ 如果使用 map / unordered_map 容器进行维护,请尽可能不要使用 [] 方式进行查询,这可能会在元素不存在时新建元素,从而导致容器内部元素数量增多,影响时空消耗。可以使用 findcount 来替代。

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

代码

typedef pair<int, int> pii;

void solve()
{
    int n, m;
    cin >> n >> m;
    
    set<pii> st; // 存储已经被方块占据的所有坐标
    
    for(int i = 1; i <= m; i++)
    {
        int x, y;
        cin >> x >> y;
        if(st.count(pii(x, y)) == 0 && st.count(pii(x+1, y)) == 0
        && st.count(pii(x, y+1)) == 0 && st.count(pii(x+1, y+1)) == 0)
        {
            st.insert(pii(x, y));
            st.insert(pii(x+1, y));
            st.insert(pii(x, y+1));
            st.insert(pii(x+1, y+1));
        }
    }
    
    cout << st.size() / 4; // 占用网格数量 / 4 = 方块数量
}

D - Teleport Maze

题意

有一个 \(H \times W\) 的网格迷宫。令 \((i,j)\) 表示第 \(i\) 行第 \(j\) 列的单元格。单元格 \((i,j)\) 的类型用字符 \(S_{i,j}\) 表示,其中每个字符的含义如下:

  • .:空
  • #:障碍物
  • 小写英文字母(a - z):扭曲的单元格

在迷宫中,您可以按照任意顺序多次执行以下两种移动操作:

  • 行走:从当前单元格出发,沿上、下、左、右四个方向之一移动到某个相邻的单元格,但不能移动到障碍物单元格或网格外。
  • 扭曲:当你位于某个扭曲的单元格时,可移动到任意一个与当前单元格拥有相同字符的扭曲的单元格位置。

判断是否有可能从 \((1,1)\) 格移动到 \((H,W)\) 格,如果有可能,找出所需的最少移动次数。

思路

一道比较简单的宽搜题。

除了正常四个方向行走以外,加入了可以通过相同英文字母来实现跳跃的走法。所以可以在输入时根据英文字母,借助 vector 等容器存储每种英文字母能够跳跃到的所有位置。

明显在步数最少的方案下,我们不会经过相同的英文字母多次。因此本题除了需要标记每个位置最多搜索一次以外,还需要一个针对英文字母的标记,确保每种字母只会看一次。

时间复杂度 \(O(H \times W)\)

代码

const int dx[4] = {-1, 1, 0, 0};
const int dy[4] = {0, 0, -1, 1};

int h, w;
char mp[1005][1005];
bool vis[1005][1005];

struct point
{
    int x, y, step;
};

vector<point> G[128]; // 根据 a~z 的 ASCII 查询所有扭曲的单元格坐标
bool vis2[128]; // 标记 a~z 每种扭曲的单元格是否已被使用过

void solve()
{
    cin >> h >> w;
    for(int i = 1; i <= h; i++)
        for(int j = 1; j <= w; j++)
        {
            cin >> mp[i][j];
            if(mp[i][j] >= 'a' && mp[i][j] <= 'z')
                G[mp[i][j]].push_back(point{i, j, 0});
        }
    
    queue<point> q;
    q.push(point{1, 1, 0});
    vis[1][1] = true;
    
    while(!q.empty())
    {
        point u = q.front(), v;
        q.pop();
        // 行走
        for(int i = 0; i < 4; i++)
        {
            v.x = u.x + dx[i];
            v.y = u.y + dy[i];
            v.step = u.step + 1;
            
            if(v.x < 1 || v.y < 1 || v.x > h || v.y > w)
                continue;
            if(vis[v.x][v.y])
                continue;
            if(mp[v.x][v.y] == '#')
                continue;
            
            q.push(v);
            vis[v.x][v.y] = true;
            if(v.x == h && v.y == w)
            {
                cout << v.step;
                return;
            }
        }
        // 扭曲
        if(mp[u.x][u.y] >= 'a' && mp[u.x][u.y] <= 'z')
        {
            if(vis2[mp[u.x][u.y]]) // 每种扭曲单元格明显只会被使用一次
                continue;
            vis2[mp[u.x][u.y]] = true;
            
            for(point v : G[mp[u.x][u.y]]) // 对于能跳跃到的所有位置
            {
                v.step = u.step + 1;
                
                if(vis[v.x][v.y])
                    continue;
                
                q.push(v);
                vis[v.x][v.y] = true;
                if(v.x == h && v.y == w)
                {
                    cout << v.step;
                    return;
                }
            }
        }
    }
    
    cout << -1;
}

E - Minimum Swap

题意

给你一个整数序列 \(P=(P _ 1,P _ 2,\ldots,P _ N)\) ,保证这是 \((1,2,\ldots,N)\) 的排列,且保证 \(P\) 不等于 \((1,2,\ldots,N)\)

您希望执行以下操作零次或多次,使 \(P\) 与序列 \((1,2,\ldots,N)\) 相匹配:

  • 选择一对满足 \(1\le i\lt j\le N\) 的整数 \((i,j)\) ,然后交换 \(P _ i\)\(P _ j\) 的值。

设整数 \(K\) 表示使 \(P\) 与序列 \((1,2,\ldots,N)\) 相匹配所需的最少运算次数。

求所有能在 \(K\) 次操作后将 \(P\) 与序列 \((1,2,\ldots,N)\) 相匹配的方案当中,可以作为第一次操作的不同整数对有多少种?

思路

我们首先考虑 \(K\) 的来源。对于一个 \(N\) 的排列,要使用最少的交换次数将其变为有序,这是一个比较经典的置换环问题。

我们将从位置 \(i\) 变为位置 \(P_i\) 称作一次置换,转化为图论问题,可以在图中让点 \(i\) 向点 \(P_i\) 连边。如果 \(P\) 排列已经有序,此时 \(i = P_i\),那么该位置的置换可以当作是点 \(i\) 形成了一个自环。我们的目标是让整张图的 \(N\) 个点都是自环,即出现 \(N\) 个环。

考虑交换操作。

  • 对于图中不是自环的某个环,不论我们交换这个环上的哪两条边的目标点(对于本题来说交换的是 \(P_i, P_j\),也就是说原图本来是 \(i \rightarrow P_i\)\(j \rightarrow P_j\),交换后变成了 \(i \rightarrow P_j\)\(j \rightarrow P_i\)),明显都会使环的总数 \(+1\)
  • 而如果我们交换来自两个环上的两条边的目标点,这样的交换反而会让环的总数 \(-1\),与我们的目标相反。

因此,每次交换我们只能选来自同一个置换环上的两条不同的边。因为环的点数=边数,因此我们可以通过循环模拟来统计每个置换环上的点数。假定某个环上的点数为 \(x\),那么这个环对答案的贡献就是 \(\text{C}_x^2 = \frac{x(x-1)}{2}\)

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

代码

int n, a[300005];
bool vis[300005];

void solve()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    
    long long ans = 0;
    
    for(int i = 1; i <= n; i++)
    {
        if(vis[i])
            continue;
        int p = a[i]; // 先向后跳一次
        int cnt = 1; // 统计环长
        vis[p] = true;
        while(p != i)
        {
            p = a[p];
            vis[p] = true;
            cnt++;
        }
        ans += 1LL * cnt * (cnt - 1) / 2; // 环上任取两点 均可作为第一次可交换的整数对
    }
    
    cout << ans;
}

F - Starry Landscape Photo

题意

夜空中有 \(N\) 颗星星,这些星星从东到西排列成一条直线。从东开始数的第 \(i\) 颗星星是这些星星中第 \(B _ i\) 亮的。

高桥决定用下面的方法拍摄一张夜空照片:

  1. 选择一对整数 \((l,r)\) 满足 \(1\le l\le r\le N\),并设置好相机,使得从东开始数的第 \(l, l+1, \dots, r\) 颗星星都能够进入画面,并且没有其他星星进入画面。
  2. 选择一个整数 \(b\) 满足 \(1\le b\le N\),打开快门,使亮度从第 \(1\) 亮到第 \(b\) 亮的星星都能被捕捉到(并且在画面内),并且其他亮度的星星都不会被捕捉到。

但是,他不能拍摄没有任何星星的照片。

求用这种方法拍摄的照片可以捕捉到多少组不同的星星照片。

思路

考虑枚举每种星星的亮度 \(x\),然后统计有多少种照片包含第 \(x\) 亮的星星,且这颗星星是照片中最不亮的星星

明显此时操作二中的 \(b = x\),照片只会显示第 \(1\) 亮到第 \(x\) 亮的所有星星。

\(L\) 表示在第 \(x\) 亮的星星左侧有多少颗比其更亮的星星,记 \(R\) 表示在其右侧有多少颗比其更亮的星星。

如果我们希望让照片中第 \(x\) 亮的这颗星星左侧出现恰好 \(a\)\((0 \le a \le L)\) 星星,右侧恰好出现 \(b\)\((0 \le b \le R)\) 星星,由于操作一选择的是一段区间,明显这样的照片是唯一的。

由于 \(a\) 的取值范围为 \([0, L]\)\(b\) 的取值范围为 \([0, R]\),两者分别有 \(L+1\)\(R+1\) 种不同的取值,明显在这种情况下能够拍出来的照片数量为 \((L+1) \times (R+1)\)

至于 \(L, R\) 的求解,可以按照亮度 \(x\) 从最亮的星星开始枚举(\(x = 1 \sim N\)),每枚举一个亮度就在这个亮度的星星所在位置标记一个 \(1\),然后借助树状数组等数据结构维护区间内的标记总和,即可快速求出某个位置左右两侧已经处理过的星星数量(即比 \(x\) 更亮的星星数量)。

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

代码

int n;
int b[500005];
int p[500005]; // p[i] 表示亮度为 i 的星星所在下标位置

int tr[500005];
int lowbit(int p)
{
    return p & -p;
}
void update(int p, int v)
{
    while(p <= n)
    {
        tr[p] += v;
        p += lowbit(p);
    }
}
int query(int p)
{
    int r = 0;
    while(p)
    {
        r += tr[p];
        p -= lowbit(p);
    }
    return r;
}

void solve()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> b[i];
        p[b[i]] = i;
    }
    long long ans = 0;
    for(int i = 1; i <= n; i++)
    {
        int l = query(p[i]);
        int r = i - 1 - l;
        ans += 1LL * (l + 1) * (r + 1);
        update(p[i], 1);
    }
    cout << ans;
}
posted @ 2025-12-13 21:59  StelaYuri  阅读(25)  评论(0)    收藏  举报