AtCoder Beginner Contest 451 ABCDF 题目解析
A - illegal
- 预估难度:
入门 - 标签:字符串
题意
判断给定字符串的长度是否为 \(5\) 的倍数。
代码
void solve()
{
string s;
cin >> s;
if(s.size() % 5 == 0)
cout << "Yes";
else
cout << "No";
}
B - Personnel Change
- 预估难度:
入门 - 标签:计数数组
题意
高桥所在的公司有 \(N\) 名员工,分别编号为 \(1, 2, \dots, N\)。
公司有 \(M\) 个部门,分别编号为 \(1, 2, \dots, M\)。
员工 \(i\) 本学期属于部门 \(A_i\),下学期属于部门 \(B_i\)。
对于每个部门 \(1, 2, \dots, M\),请求出下学期的人数减去本学期的人数。
思路
分别用两个计数数组统计每个部门本学期的人数与下学期的人数,取差输出后即可。
代码
int a[105], b[105];
void solve()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
int x, y;
cin >> x >> y;
a[x]++;
b[y]++;
}
for(int i = 1; i <= m; i++)
cout << b[i] - a[i] << "\n";
}
C - Understory
- 预估难度:
普及- - 标签:数据结构(堆)
题意
高桥正在管理花园里的树。最初,花园里没有树。
接下来依次给出 \(Q\) 次操作,每次操作都是以下两种类型之一。在处理完每一次操作后,请输出此时花园中的树木数量。
1 h:向花园中添加一棵高度为 \(h\) 的新树。2 h:移除花园中所有高度不超过 \(h\) 的树木。
思路
可以借助一个小根堆维护所有现有的树的高度。
加一棵树可以视作加一个新数字进堆。
删除所有高度不超过 \(h\) 的树,可以从小根堆的堆顶开始,只要堆顶还 \(\le h\) 就继续删除堆顶,直到堆空或者堆顶 \(\gt h\) 为止。
由于每个数只会进堆/出堆一次,因此时间复杂度为 \(O(Q \log Q)\)。
代码
void solve()
{
int q;
cin >> q;
priority_queue<int, vector<int>, greater<int>> pq;
while(q--)
{
int op, h;
cin >> op >> h;
if(op == 1)
pq.push(h);
else
{
while(!pq.empty() && pq.top() <= h)
pq.pop();
}
cout << pq.size() << "\n";
}
}
D - Concat Power of 2
- 预估难度:
普及/提高- - 标签:数据结构、数学
题意
如果一个正整数 \(N\) 满足以下条件,我们称它为好整数。
- \(N\) 可以通过选择一些 \(2\) 的幂次(\(1, 2, 4, 8, 16, \dots\)),并将它们前后连接成字符串后构造得到。允许重复选择。
请找出第 \(N\) 小的好整数。保证答案不超过为 \(10^9\) 。
思路
样例已提示 \(10^9\) 内符合条件的整数个数比百万稍多些,可以考虑构造。
考虑构造出所有符合条件的整数,取第 \(N\) 小。
由于过程中可能构造出重复整数(例如 \(2^7 = 128\),\(2^02^12^3=128\)),因此采用 set 辅助对构造过程进行去重。
从 \(0\) 开始(将 \(0\) 视作第 \(0\) 小),每次在当前已经构造出来但还没看过的整数中选择一个最小的整数(第 \(i\) 次选择的整数就是第 \(i-1\) 小),在其末尾继续拼接 \(2^{0 \sim 29}\),得出的新整数重新放进集合进行排序与去重。然后将选择的这个数从集合中去除。
重复 \(N\) 次操作后,集合内出现的最小整数即为答案。
时间复杂度 \(O(N\log v)\),\(v = 10^9\)。
代码
// 取 u 的长度
int len(int u)
{
int r = 0;
while(u > 0)
{
u /= 10;
r++;
}
return r;
}
// 将 v 拼在 u 后面
// 如果结果超出 1e9 则返回 -1
int cal(int u, int v)
{
int l = len(v);
while(l--) // 让 u 乘上 10^l
{
if(u > 1e8)
return -1;
u = u * 10;
}
if(u + v > 1e9)
return -1;
return u + v;
}
void solve()
{
int n;
cin >> n;
set<int> st;
st.insert(0);
for(int i = 1; i <= n; i++)
{
int u = *st.begin(); // 取出当前最小值
st.erase(st.begin()); // 删除当前最小值
for(int j = 0; j < 30; j++)
{
int v = cal(u, 1 << j); // 在 u 后面拼上 2^j
if(v != -1)
st.insert(v);
}
}
cout << (*st.begin()); // n 次操作已删除前 n-1 小,此时的最小即为答案
}
F - Make Bipartite 3
- 预估难度:
普及+/提高 - 标签:带权并查集
题意
有一张包含 \(N\) 个点的无向图 \(G\),点编号分别为 \(1\) 到 \(N\),初始无边。
给定 \(Q\) 次操作,第 \(i\) 次操作会向图 \(G\) 添加一条连接点 \(u_i\) 和 \(v_i\) 的边。
在处理完每一次操作后,请立即确定是否有可能将 \(G\) 的每个点染成白色或黑色,从而满足以下条件。如果有可能,请找出染成黑色的点的最小数量。
- 每条边所连接的两个点有着不同的颜色。
思路
无向图的黑白染色,即判断图是否是一张二分图。
如果不是二分图,说明已经出现奇环,此后所有操作均为 \(-1\)。
而如果是一张二分图,此时图中有可能存在多个连通块。对于每个连通块,都可以假设去尝试黑白染色一次,取两种颜色各自涂的点数的较小值计入最终答案。
由于每次加边可能会对连通块产生影响,考虑借助并查集维护,并将每个集合假设涂色的信息记录在集合的根结点上。
- 例如,用 \(p\) 表示与根结点同色的点数,\(q\) 表示与根结点异色的点数。那么每个集合对答案的贡献就是 \(\min(p, q)\)。
然后考虑加边合并。
当合并两点 \(u, v\) 时,如果两点此时位于不同的集合内,那么本次合并就需要考虑这条边对两个集合答案的影响。
首先集合肯定需要合并,然后分类讨论 \(u, v\) 到各自根结点的距离之和,记作 \(S\),那么 \(S+1\) 便是将 \(u, v\) 这两个点之间的边连通后,两个根结点之间的距离。
- 如果 \(S+1\) 是一个奇数,此时两个根结点不能涂成相同的颜色。在集合合并时,被合并的集合的 \(p, q\) 需要交叉添加到目标集合的 \(q, p\) 上。
- 如果 \(S+1\) 是一个偶数,此时两个根结点必须涂成相同的颜色。在集合合并时,两个集合的 \(p, q\) 对应相加即可。
合并两点 \(u, v\) 时,如果两点此时位于相同的集合内,就得判断这条边所导致出现的环长奇偶性。
由于 \(u, v\) 之间现在也存在一条边,为了保证答案存在,环长必须是偶数。如果记 \(D_u, D_v\) 分别表示 \(u, v\) 两点到当前集合根结点的距离,也就是要保证 \(D_u + D_v + 1\) 是一个偶数。或者说,\(D_u\) 与 \(D_v\) 的奇偶性不同。
倘若上述条件不满足,则奇环出现,之后所有操作的答案均为 \(-1\)。
由于这里还需要知道每个点到根结点的距离(其实只需要知道距离奇偶性即可),因此考虑使用带权并查集维护,将每条边的权值设置为 \(1\) 即可。
时间复杂度 \(O(Q \log N)\)。
代码
int fa[200005];
int val[200005]; // i -> fa[i] 的边权
int p[200005], q[200005];
// p[i], q[i] 表示以 i 作为集合根结点时,集合内与 i 距离为偶数/奇数的点数
int ans = 0; // 当前答案
int find(int p)
{
if(p == fa[p])
return p;
int root = find(fa[p]);
val[p] += val[fa[p]]; // 将边权修改为到根结点的总权值
return fa[p] = root;
}
// a 到 b 连一条边权为 1 的边
bool merge(int a, int b)
{
int aa = find(a), bb = find(b);
if(aa == bb) // 已经在一个集合内
{
// 此时两点到根结点的距离奇偶性必须不同
// 如果奇偶性相同,加上现在新加的边,将会出现奇环
return (val[a] - val[b]) % 2 != 0;
}
// 统计答案
ans -= min(p[aa], q[aa]);
ans -= min(p[bb], q[bb]); // 集合合并前,将旧答案去掉
if((val[a] + val[b] + 1) % 2 == 0) // 两个根结点同色
{
p[bb] += p[aa];
q[bb] += q[aa];
}
else // 两个根结点异色
{
p[bb] += q[aa];
q[bb] += p[aa];
}
ans += min(p[bb], q[bb]); // 加入新答案
// 让 bb 成为 aa 的父结点
val[aa] = -val[a] + 1 + val[b];
fa[aa] = bb;
return true;
}
void solve()
{
int N, Q;
cin >> N >> Q;
// 并查集初始化
for(int i = 1; i <= N; i++)
{
fa[i] = i;
p[i] = 1; // i 到 i 距离为 0,偶数
}
bool flag = false; // 判断是否出现了奇环
while(Q--)
{
int u, v;
cin >> u >> v;
if(flag || !merge(u, v))
{
flag = true;
cout << -1 << "\n";
}
else
cout << ans << "\n";
}
}

浙公网安备 33010602011771号