Educational Codeforces Round 100 (Rated for Div. 2) 题解
A. Dungeon
【题面】
操作 \(1\):每一次让一个 \(a_i\) 变成 \(a_i-1\)
操作 \(2\):每 \(7\) 此操作 \(1\) 可以让所有 \(a_i\) 都变成 \(a_i-1\)
【思路】
1.容易发现对于每次操作 \(2\) 和上一次操作 \(2\) 之间会对所有怪物的血量综合造成 \(9\) 的伤害。
2.题目只问了怪物是否可以在任意一次操作 \(2\) 时被全部清空血量,也就是在任意一次 \(9\) 的伤害后,血量综合会归 \(0\)。
3.判断一下怪物血量综合是否是 \(9\) 的倍数。
4.但是如果有一个人的血量不足以等到其他怪物血量清空就已经被清空,那不妨就让所有人的血量先减去计算得到的全体伤害。如果有人不够减,那么答案肯定是 NO。
【实现】
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
void solve(){
ll a, b, c;
cin >> a >> b >> c;
ll sum = a+b+c;
if(sum%9!=0){
cout << "NO\n";
return ;
}
if(min(a, min(b, c)) < sum/9){
cout << "NO\n";
}
else{
cout << "YES\n";
}
}
int main(){
int T;
cin >> T;
while(T--){
solve();
}
}
B. Find The Array
【题面】
给定一个长度为 \(n\) 的数组 \(A\),构造一个数组 \(b\) 满足
- \(b_i \mid b_i+1\) 或者 \(b_i+1 \mid b_i\)
- \(2\sum_{i=1}^{n} \lvert a_i - b_i \rvert \leq \sum_{i=1}^{n} a_i\)
【思路】
1.首先考虑到如果要让所有 \(\lvert a_i - b_i \rvert\) 的和小于等于 \(\frac{1}{2} \sum_{i=1}^{n} a_i\) 还不如直接让每一个 \(2b_i \leq a_i\)。
2.考虑如何得到最大的 $2b_i \leq a_i $,可以让 \(b_i\) 每一次乘 \(2\) 去判断是否小于等于 \(a_i\),这样的事件复杂度大概是 \(O(n\log a_i)\)
【实现】
#include <bits/stdc++.h>
using namespace std;
inline int read(){
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar(); }
while(ch >= '0' && ch <= '9'){ x = x * 10 + (ch ^ 48); ch = getchar(); }
return x * f;
}
void solve(){
int n =read();
vector<int>a(n+1);
for(int i=1; i<=n; ++i){
cin >> a[i];
}
for(int i=1; i<=n; ++i){
int j = 1;
while(j*2 <= a[i])j *=2;
cout << j << ' ';
}
cout << '\n';
}
int main(){
int T = read();
while(T--){
solve();
}
return 0;
}
C.Busy Robot
【题面】
你有一个机器人,初始位置(0 秒),它在数轴上的位置为 0。它的速度是 1 单位每秒。
它会接收 \(n\) 个命令:在第 \(t_i\) 秒移动到 \(x_i\)。在它移动的时候,会忽视你的所有命令。
定义第 \(i\) 个命令是成功的,当且仅当在第 \([t_i, t_{i+1}]\) 秒内机器人到达过 \(x_i\)。(我们定义 \(t_{n+1}\) 为正无穷)
你的任务就是求出有多少条命令是成功的。
【思路】
- 首先需要知道哪些命令被忽视,这样才能确定机器人的实际运动轨迹。
- 一条命令被忽视的条件是:上一条有效的命令执行期间,机器人还没有到达目标位置。可以用变量记录上一条有效的命令,从而构建机器人的完整运动轨迹。
3.如果以时间为横轴,位置为纵轴,可以画出机器人的运动轨迹为折线图。折线图的拐点最多有 \(n\) 个,因为最多有 \(n\) 条有效命令。
4.对于第 \(i\) 条命令,我们需要检查在时间区间 \([t_i, t_{i+1}]\) 内,机器人的运动轨迹是否经过了位置 \(x_i\)。
这样就将问题转化为:在已知机器人实际运动轨迹的情况下,检查每个命令要求的位置是否在对应时间区间内被经过。
【实现】
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = INT_MAX;
void solve() {
int n; cin >> n;
int cur = 0;
vector<ll> t(n);
vector<ll> x(n);
for(int i=0; i<n; ++i)cin >> t[i] >> x[i];
int ans = 0;
for(int i=0; i<n; ++i){
ll dif = abs(cur - x[i]);
int nex = i + 1;
while (nex < n && t[nex] < t[i] + dif)nex++;
ll nexdif = INF;
if (i + 1 < n)nexdif = t[i + 1] - t[i];
if (dif <= nexdif)ans++;
for (int j = i + 1; j < nex; j++) {
ll tdif = t[j] - t[i];
ll tdif2 = INF;
if (j + 1 < n)tdif2 = t[j + 1] - t[i];
ll le, ri;
if (cur <= x[i]) {
le = min(cur + tdif, x[i]);
ri = min(cur + tdif2, x[i]);
}
else {
le = max(cur - tdif2, x[i]);
ri = max(cur - tdif, x[i]);
}
if (le <= x[j] && x[j] <= ri)ans++;
}
cur = x[i];
i = nex - 1;
}
cout << ans << "\n";
}
int main(){
int T;
cin >> T;
while(T--){
solve();
}
}
D. Pairs
【题面】
我们有 \(2m\) 个整数 \(1, 2, \dots, 2m\),需要将其分成 \(m\) 对,然后选择其中 \(z\) 对,取出其中的较小数,并取出其余 \(m-z\) 对中的较大数,使得最终取出的数组成的集合为 \(\{b_1, b_2, \dots, b_m\}\)。问有多少个满足题意的 \(z\)。
【思路】
-
直接对 x 计数非常困难。一个经典的转化思路是,将计数问题转化为判定问题:先固定一个
x 值,判断它是否合法。 -
直接对 \(z\) 计数非常困难。经典的转化思路是:将计数问题转化为判定问题 - 先固定一个 \(z\) 值,判断它是否合法。
-
对于一个固定的 \(z\),如何判定其是否可行?
- 要选出 \(z\) 个来自 \(b\) 的数作为"较小数",最明智的选择是挑出 \(b\) 数组中最小的 \(z\) 个数
- 要选出 \(m-z\) 个来自 \(b\) 的数作为"较大数",最明智的选择是挑出 \(b\) 数组中最大的 \(m-z\) 个数
- 让 \(b\) 中最小的 \(z\) 个数成为"较小数",它们必须与不在 \(b\) 中的数量最大的 \(z\) 个进行匹配
- 让 \(b\) 中最大的 \(m-z\) 个数成为"较大数",它们必须与不在 \(b\) 中的数量最小的 \(m-z\) 个进行匹配
-
这种"最小的配最大的"策略可以形式化为:
- 将两组数各自排序后,按顺序一一配对
- 这个贪心匹配模型在其他题目中也有出现(如 Codeforces 1156C)
5.合法的 \(z\) 值构成了一个连续的区间。如果 \(z\) 合法,那么比它更宽松的条件(如 \(z-1\))通常也合法。既然解集是一个连续区间,我们可以通过二分答案来快速找到这个合法区间的左右端点,从而确定合法 \(z\) 的数量。
【实现】
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int t;
int n, b[N], c[N];
bool hav[N * 2];
int lef[N], rig[N];
bool ok_left(int x) {
for (int i = 1; i <= x; ++i)
if (b[i] > c[n - x + i]) return false;
return true;
}
bool ok_right(int x) {
for (int i = x + 1; i <= n; ++i)
if (b[i] < c[i - x]) return false;
return true;
}
void solve() {
cin >> n;
for(int i=1; i<=2*n; ++i)hav[i] = 0;
for(int i=1; i<=n; ++i){
cin >> b[i];
hav[b[i]] = 1;
}
int cnt =0 ;
for(int i=1; i<=2*n; ++i){
if(!hav[i]){
c[++cnt] = i;
}
}
int rooms = 0;
int l =1, r = n, ansl = 0, ansr = n;
while(l<=r){
int mid = (l+r)/2;
if(ok_left(mid)){
ansl = mid;
l = mid + 1;
}
else{
r = mid - 1;
}
}
l = 0, r = n-1;
while(l <= r){
int mid = (l+r) / 2;
if(ok_right(mid)){
ansr = mid;
r = mid - 1;
}
else{
l = mid + 1;
}
}
cout << max(0, ansl - ansr + 1) << '\n';
}
int main() {
ios::sync_with_stdio (false);
cin.tie(nullptr);
cin >> t;
while (t--)
solve ();
return 0;
}
E.PLan of Lectures
【题面】
这是一个构造性问题,要求输出一个满足特定条件的排列。
- 对于每个 \(i\),\(p_i\) 必须排在 \(i\) 前面。这是一个典型的拓扑排序依赖关系。
- 对于给定的 \(k\) 个数对 \((x,y)\),\(y\) 必须紧跟在 \(x\) 的后面。这是一个强制邻接关系。
【思路】
如果只有约束一,直接进行拓扑排序即可。问题的关键在于如何将约束二这个强限制融入到拓扑排序的框架中。
2.
约束二比"\(x\) 在 \(y\) 之前"要强得多,它规定了 \(x\) 和 \(y\) 在最终排列中的物理位置是相邻的。这些强制邻接关系会把离散的元素"粘"在一起,形成若干条内部顺序完全固定的"元素链"。若有 \((a,b)\) 和 \((b,c)\),则它们在最终排列中必然以 \(\ldots, a, b, c, \ldots\) 的形式作为一个整体出现。
3.既然这些链不可分割,我们就不应再把单个元素作为排序的对象,而应该把这些"元素链"作为排序的基本单位。这种将关系紧密的节点捆绑成一个"超级节点"再进行处理的思想,与图论中的"缩点"算法异曲同工。
4.根据强制邻接关系,遍历并构建出所有的元素链。将每个链视为一个节点,构建一张新图。如果原图中存在依赖关系 \(p_i \rightarrow i\),且 \(p_i\) 和 \(i\) 分属不同的链 A 和 B,就在新图中连一条边 A→B。在这张由"链"构成的新图上,执行一次标准的拓扑排序。按拓扑序依次输出每个链中的所有元素。
【实现】
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const LL mod = 1e9 + 7;
const int N = 300005;
int p[N];
int find(int x) {
return p[x] == x ? x : p[x] = find(p[x]);
}
int a[N], b[N], c[N], in[N], ans[N];
vector<int> G[N], H[N];
bool vis[N];
int main() {
int n, m;
scanf("%d%d", &n, &m);
int root;
for (int i = 1; i <= n; i++) {
int fa;
scanf("%d", &fa);
p[i] = i;
if (fa == 0)
root = i;
else
H[fa].push_back(i);
}
while (m--) {
int x, y;
scanf("%d%d", &x, &y);
a[x] = y;
b[y] = x;
x = find(x), y = find(y);
if (x == y) return puts("0"), 0;
p[x] = y;
}
for (int i = 1; i <= n; i++) {
if (b[i] == 0) c[find(i)] = i;
for (auto v : H[i]) {
if (find(i) != find(v)) {
G[find(i)].push_back(find(v));
in[find(v)]++;
}
}
}
queue<int> q;
q.push(find(root));
int ct = 0;
while (q.size()) {
int u = q.front();
q.pop();
int t = c[u];
while (t) {
ans[++ct] = t;
t = a[t];
}
for (auto v : G[u]) {
in[v]--;
if (in[v] == 0) {
q.push(v);
}
}
}
if (ct != n) return puts("0"), 0;
vis[root] = 1;
for (int i = 1; i <= n; i++) {
if (vis[ans[i]] == 0) return puts("0"), 0;
for (auto v : H[ans[i]]) vis[v] = 1;
}
for (int i = 1; i <= n; i++) printf("%d%c", ans[i], " \n"[i == n]);
return 0;
}