atcoder ABC 451 题解

atcoder abc #451 题解

A - illegal

问题陈述

在Takahashi居住的AtCoder国家,有一条奇特的法律:"你不能写一个长度是 555 的倍数的字符串。"

Takahashi写了一个字符串 SSS 。确定他是否违反了这条法律。

约束

  • SSS 是长度在 111 到 101010 之间的字符串,包括。

  • SSS 由小写英文字母组成。

输入

输入来自标准输入,格式如下:

SSS

输出

如果 Takahashi 违反了法律,输出 Yes;否则,输出 No

解题思路

我们使用 string 类型来储存 SSS,然后使用 size 函数来计算 SSS 的长度,如果 SSS 的长度是 5 的倍数就直接输出 Yes

代码

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

using namespace std;

int main()
{
    string s;
    cin >> s;
    if (s.size() % 5 == 0) // 判断字符串长度是不是 5 的倍数
        cout << "Yes";
    else
        cout << "No";
    return 0;
}

B - Personnel Change

问题陈述

Takahashi所在的公司有 NNN 名员工,每个员工的编号都来自 1,2,...,N1, 2, \dots, N1,2,...,N 。公司有 MMM 个部门,称为部门 1,2,...,M1, 2, \dots, M1,2,...,M 。

员工 iii 本学期属于部门 AiA_iAi ,下学期将属于部门 BiB_iBi 。

对于每个部门 1,2,...,M1, 2, \dots, M1,2,...,M ,求下一学期的成员数减去这一学期的成员数。

约束

  • 1≤N≤1001 \leq N \leq 1001≤N≤100

  • 1≤M≤1001 \leq M \leq 1001≤M≤100

  • 1≤Ai≤M1 \leq A_i \leq M1≤Ai≤M

  • 1≤Bi≤M1 \leq B_i \leq M1≤Bi≤M

  • 所有输入值均为整数。

输入

输入来自标准输入,格式如下:

NNN MMM A1A_1A1 B1B_1B1 A2A_2A2 B2B_2B2 ⋮\vdots⋮ ANA_NAN BNB_NBN

输出

输出 MMM 行。 第 jjj 行应该包含部门 jjj 的答案。

解题思路

开辟两个数组 a,ba, ba,b,a[i]a[i]a[i] 表示第 iii 个部分在本学期有多少人,b[i]b[i]b[i] 则表示下一个学期第 iii 个部门的人数。

每次输入某一个员工两个学期的部门时,我们就维护一下两个数组就行了。

代码

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

using namespace std;

const int maxn = 105;
int n, m, a[maxn], b[maxn];

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        int x, y;
        cin >> x >> y;
        a[x]++; // 这个学期在 x
        b[y]++; // 下个学期在 y
    }
    for (int i = 1; i <= m; i++)
        cout << b[i] - a[i] << '\n';
    return 0;
}

C - Understory

问题陈述

高桥正在管理他花园里的树木数量。最初,花园里没有树。

QQQ 查询按顺序给出。每个查询都是以下两种类型之一。处理完每个查询后,立即输出花园中当前树的数量。

  • 1 h:在花园中添加一棵高度为 hhh 的新树。

  • 2 h:移除花园中目前高度不超过 hhh 的所有树木。

约束

  • 1≤Q≤3×1051 \le Q \le 3 \times 10^51≤Q≤3×105。

  • 1≤h≤1091 \le h \le 10^91≤h≤109。

  • 所有输入值均为整数。

输入

输入来自标准输入,格式如下:

QQQ query1\text{query}_1query1 query2\text{query}_2query2 ⋮\vdots⋮ queryQ\text{query}_QqueryQ

queryi\text{query}_iqueryi ,表示第 iii 个查询,是以下两种类型之一:

111 hhh
222 hhh

输出

输出 QQQ 行。

第 iii 行应该包含第 iii 次处理后花园里的树的数量。

解题思路

使用 multiset 来实现数目高度的维护,删除的操作时间复杂度为 O(log⁡n)O(\log n)O(logn) 。

代码

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

using namespace std;

int q;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    multiset<int> s;
    cin >> q;
    while (q--)
    {
        int opt, h;
        cin >> opt >> h;
        if (opt == 1)
            s.insert(h); // 插入一个新的树
        else
        {
            while (!s.empty()) // 每一次都试着删除最矮的那一棵树
            {
                if (*s.begin() <= h) // 如果最矮的那棵树高度小于 h,直接删除
                    s.erase(s.begin());
                else // 最矮的都大于 h,剩下的肯定也大于 h
                    break;
            }
        }
        cout << s.size() << '\n'; // 输出还有几棵树
    }
    return 0;
}

D - Concat Power of 2

问题陈述

如果一个正整数满足下列条件,我们称它为好整数。

---条件:选择 222 ( 1,2,4,8,16,...1, 2, 4, 8, 16, \dots1,2,4,8,16,... )的一个或多个幂(允许重复和重新排序),将它们连接成字符串,并将结果解释为整数。

求第 NNN 小好整数是多少。可以保证第 NNN 小的好整数不超过 10910^9109 。

约束

  • NNN 是一个正整数。
  • 第 NNN 小的好整数最多为 10910^9109 。

输入

输入来自标准输入,格式如下:

NNN

输出

输出答案。

解题思路

搜索模板题,我们用一个优先队列来维护所有已经处理出来的好整数,小的先出队,大的后出队,再加上亿点点剪枝就可以过了。

具体实现看代码。

代码

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

using namespace std;

int n;

struct cmp // 定义优先队列的排序规则,大的数字在后面,小的在前面
{
    bool operator()(string a, string b) const
    {
        if (a.size() == b.size())
            return a > b;
        return a.size() > b.size();
    }
};

int main()
{
    cin >> n;
    priority_queue<string, vector<string>, cmp> q;
    vector<string> s;
    map<string, int> M; // 看看这个数字是不是出现过
    long long a = 1;
    while (a <= 1e9) // 把所有 2 的倍数都处理出来
    {
        string tmp = to_string(a);
        s.push_back(tmp);
        q.push(tmp);
        M[tmp] = 1; // 出现过了
        a *= 2;
    }
    while (n)
    {
        string u = q.top(); // 每次都找最小的那一个
        q.pop();
        n--;
        if (n == 0) // 找到第 n 小的数字
        {
            cout << u;
            break;
        }
        for (string w : s) // 做拼接,找新的好整数
        {
            string v = u + w;
            if (v.size() < 10 && M[v] == 0)
                q.push(v), M[v] = 1;
        }
    }
    return 0;
}

E - Tree Distance

问题陈述

判断是否存在顶点 NNN 的加权无向树满足以下条件。

---对于任意两个带 1≤i<j≤N1 \le i \lt j \le N1≤i<j≤N 的整数 iii 和 jjj ,顶点 iii 和 jjj 之间的距离为 Ai,jA_{i,j}Ai,j 。

在这里,点 iii 和 jjj 之间的距离定义为连接这两个点的唯一简单路径上的边的权值之和。

约束

  • 2≤N≤30002 \le N \le 30002≤N≤3000

  • 1≤Ai,j≤99991 \le A_{i,j} \le 99991≤Ai,j≤9999

  • 所有输入值均为整数。

输入

输入来自标准输入,格式如下:

NNN A1,2A_{1, 2}A1,2 A1,3A_{1, 3}A1,3 ...\ldots... A1,NA_{1, N}A1,N A2,3A_{2, 3}A2,3 ...\ldots... A2,NA_{2, N}A2,N ⋮\vdots⋮ AN−1,NA_{N-1,N}AN−1,N

输出

如果存在满足条件的树,则输出 Yes;否则,输出 No

解题思路

我们可以将题目中给出的任意两点距离分为两类:

  1. 两点直接相连,距离即为这条边的权值;
  2. 两点不直接相连,距离由路径上多条边的权值之和构成。

解题的核心思路是:从小到大枚举所有点对的距离。

若当前枚举到的两点尚未连通,则这对点的距离必然对应直接相连的边(即情况 1)。

原因很直观:边权均为正数,若两点距离是经过其他点的路径长度(情况 2),则路径上所有边的长度都严格小于该距离,会在更早的枚举阶段就被建立并连通两点。因此,未连通时点对的距离一定是直接边权,且会最先被处理。

代码

cpp 复制代码
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f

using namespace std;

const int maxn = 3005;
int n, dis[maxn][maxn], tot, fa[maxn];
vector<pair<int, int>> E[maxn];  // 存储构造出来的树的邻接表

// 边结构体:存储点x、点y、两点间距离w
struct edge
{
    int x, y, w;
    // 按距离从小到大排序
    bool operator<(const edge &tmp) const
    {
        return w < tmp.w;
    }
} e[maxn * maxn];  // 存储所有点对的距离

// 并查集查找(路径压缩)
int find(int k)
{
    if (fa[k] == k)
        return k;
    return fa[k] = find(fa[k]);
}

// 并查集合并
void merge(int x, int y)
{
    int fx = find(x), fy = find(y);
    if (fx != fy)
        fa[fx] = fy;
}

// DFS:从root出发遍历整棵树,计算root到所有点的真实距离
void dfs(int root, int x, int fa, int W)
{
    dis[root][x] = W;  // 记录真实距离
    for (auto [v, w] : E[x])  // 遍历邻接点
        if (v != fa)          // 不回头走父节点
            dfs(root, v, x, W + w);  // 递归,累加边权
}

int main()
{
    cin >> n;
    // 初始化并查集
    for (int i = 1; i <= n; i++)
        fa[i] = i;

    // 输入所有 i<j 的距离,存入边数组
    for (int i = 1; i < n; i++)
        for (int j = i + 1; j <= n; j++)
        {
            int w;
            cin >> w;
            e[++tot] = {i, j, w};
        }

    // 核心步骤1:按距离从小到大排序所有点对
    sort(e + 1, e + tot + 1);

    // 核心步骤2:用 Kruskal 算法构造树(最小生成树思想)
    for (int i = 1; i <= tot; i++)
    {
        auto [x, y, w] = e[i];
        // 如果两点不连通,说明这条边是树边,直接加入
        if (find(x) != find(y))
        {
            merge(x, y);
            E[x].emplace_back(y, w);
            E[y].emplace_back(x, w);
        }
    }

    // 核心步骤3:DFS 遍历构造出的树,计算所有点对的真实距离
    for (int i = 1; i <= n; i++)
        dfs(i, i, i, 0);

    // 核心步骤4:验证所有输入距离 是否等于 树上真实距离
    for (int i = 1; i <= tot; i++)
    {
        auto [x, y, w] = e[i];
        if (dis[x][y] != w)  // 只要有一个不匹配,就不合法
        {
            cout << "No";
            return 0;
        }
    }

    // 全部匹配,输出合法
    cout << "Yes";
    return 0;
}

F - Make Bipartite 3

问题陈述

存在一个无向图 GGG ,其中 NNN 个顶点编号为 111 到 NNN ,无边。

QQQ 查询。在 iii 次查询中,在图 GGG 中添加一条连接顶点 uiu_iui 和 viv_ivi 的边。

在处理完每个查询后,立即判断是否可以将 GGG 的每个顶点都涂成白色或黑色,以满足以下条件,如果可能,找出涂成黑色的顶点的最小可能数量。

  • 对于每条边,两个端点具有不同的颜色。

约束

  • 2≤N≤2×1052 \le N \le 2 \times 10^52≤N≤2×105

  • 1≤Q≤2×1051 \le Q \le 2 \times 10^51≤Q≤2×105

  • 1≤ui<vi≤N1 \le u_i \lt v_i \le N1≤ui<vi≤N

  • (ui,vi)≠(uj,vj) (i≠j)(u_i, v_i) \ne (u_j, v_j) \ (i \ne j)(ui,vi)=(uj,vj) (i=j)

  • 所有输入值均为整数。

输入

输入来自标准输入,格式如下:

NNN QQQ u1u_1u1 v1v_1v1 u2u_2u2 v2v_2v2 ⋮\vdots⋮ uQu_QuQ vQv_QvQ

输出

输出 QQQ 行。

在处理完第 iii 行查询后, 第 iii 行应该包含如下图 GGG 的值。

  • 如果一个有效的着色是可能的,最小可能的顶点数量被涂成黑色。

  • 如果没有有效的着色,则为 −1-1−1 。

解题思路

并查集模板题,因为这里可以把正常图转化成一个二分图,所以我们让同一侧的点在相同的并查集里面。

每一次连边的时候,我们就在两个并查集里面找数量更少的那一个加入到答案里面,让该并查集里面所有的点都被染成黑色。

具体实现看代码。

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 2e5 + 5;
// nxt[x]:x 的配对点(二分图中与x异色的对应点,保证每个点最多一个配对,维持二分结构)
// fa[x]:并查集父节点,维护连通块
// n:顶点数,q:查询(加边)次数
// cnt[x]:并查集根为x的连通块大小(维护二分两侧的点数)
int nxt[maxn], fa[maxn], n, q, cnt[maxn];

// 并查集查找(带路径压缩,加速查询)
int find(int x)
{
    if (x == fa[x])
        return x;
    return fa[x] = find(fa[x]);
}

// 并查集合并(合并两个连通块,同步维护连通块大小)
void merge(int x, int y)
{
    int fx = find(x), fy = find(y);
    if (fx != fy)
        fa[fx] = fy, cnt[fy] += cnt[fx];
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> q;

    // 初始化并查集:每个顶点独立成连通块,初始大小为1
    for (int i = 1; i <= n; i++)
    {
        fa[i] = i;
        cnt[i] = 1;
    }

    int ans = 0;  // 维护当前黑色顶点的最小数量(全局答案)
    int f = 0;    // 标记是否出现非法情况(出现奇环,无法二分染色)
    while (q--)
    {
        int x, y;
        cin >> x >> y;

        // 情况1:已非法 或 x、y在同一连通块(同色)→ 加边必成奇环,非法
        if (f || find(x) == find(y))
        {
            f = 1;  // 标记非法,后续所有查询均输出-1
            cout << -1 << '\n';
            continue;
        }

        // 情况2:x与y的配对点不在同一连通块 → 可合法加边,更新二分结构
        if (find(x) != find(nxt[y]))
        {
            // 先减去x、y原有配对的贡献(旧配对的最小点数,需撤销)
            ans -= min(cnt[find(x)], cnt[find(nxt[x])]);
            ans -= min(cnt[find(y)], cnt[find(nxt[y])]);

            // 合并x与y的原配对点(维持二分图异色关系)
            if (nxt[y]) merge(x, nxt[y]);  // nxt[y]不为0时才合并(避免空配对)
            if (nxt[x]) merge(y, nxt[x]);  // 同理,nxt[x]不为0时合并

            // 更新x、y的配对关系(绑定为异色配对,构成二分边)
            nxt[x] = y;
            nxt[y] = x;

            // 加上新配对的贡献(合并后连通块的最小点数)
            ans += min(cnt[find(x)], cnt[find(y)]);
        }

        // 输出当前合法答案(非法时已提前输出-1)
        cout << ans << '\n';
    }
    return 0;
}
相关推荐
_日拱一卒2 小时前
LeetCode:和为K的子数组
算法·leetcode·职场和发展
周可温8412 小时前
动手学RAG
算法
周可温8412 小时前
Transformer 深度理解与动手实现
算法
南境十里·墨染春水2 小时前
C++传记 详解单例模式(面向对象)
开发语言·c++·单例模式
扶摇接北海1762 小时前
洛谷:B4488 [语言月赛 202602] 甜品食用
数据结构·c++·算法
cui_ruicheng2 小时前
C++智能指针:从 RAII 到 shared_ptr 源码实现
开发语言·c++
直有两条腿2 小时前
【机器学习】K-Means 算法
算法·机器学习·kmeans
A923A2 小时前
【洛谷刷题 | 第十天】
算法·洛谷·sprintf·sscanf
Mr_Xuhhh2 小时前
LeetCode 热题 100 刷题笔记:数组与排列的经典解法
数据结构·算法·leetcode