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(logn)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),则路径上所有边的长度都严格小于该距离,会在更早的枚举阶段就被建立并连通两点。因此,未连通时点对的距离一定是直接边权,且会最先被处理。
代码
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;
}