板刷 Edu
板刷 Edu
Educational Codeforces Round 100
A.Dungeon
弱智题,但是我一眼上去不会。
一轮操作总共造成 点伤害,所以符合要求的一个必要条件是 ,还要注意每个怪物每轮至少受到一点伤害,生命最小的不能被刮死,所以还要 ,两个合起来是充要的。
void solve() {
int a, b, c; cin >> a >> b >> c;
int mn = min({a, b, c}), sum = a + b + c;
if(sum % 9 == 0 && mn >= sum / 9) cout << "Yes\n";
else cout << "No\n";
}
B.Find The Array
注意到式子里有个 ,吸引人从 的次幂考虑。
发现可以对于每个 ,匹配小于等于它的最大 的幂,这样 ,所以 ,符合要求。
void solve() {
cin >> n;
rep(i, 1, n) {int x; cin >> x; cout << (1 << __lg(x)) << " \n"[i == n];}
}
C.Busy Robot
小模拟,动态更新当前正在执行的操作,判断指令是否被忽略。注意到对于 这个区间,执行的操作要么完全包含这个区间,要么在这个区间中结束。所以对于每一个区间,机器人走过的区间是容易计算的。
假设当前在执行的指令开始时间、结束时间、起始位置、方向分别是 ,那么对于 ,机器人位置为 ,代入 计算,检查 是否在这个区间内即可。注意特判 超过 的情况。
int n;
pair<ll, ll> a[N], b[N];
void solve() {
cin >> n; rep(i, 1, n) cin >> a[i].fi >> a[i].se;
ll ed = 0, from, to = 0, d, st;
rep(i, 1, n) {
if(ed <= a[i].fi) {
from = to, to = a[i].se; ed = a[i].fi + abs(from - to), st = a[i].fi;
d = to - from > 0 ? 1 : -1;
}
b[i].fi = from + d * (a[i].fi - st);
if(i < n) b[i].se = a[i + 1].fi > ed ? to : from + d * (a[i + 1].fi - st);
else b[i].se = to;
if(b[i].fi > b[i].se) swap(b[i].fi, b[i].se);
}
int ans = 0;
rep(i, 1, n) if(b[i].fi <= a[i].se && a[i].se <= b[i].se) ans++;
cout << ans << "\n";
}
D.Pairs
首先有直观的贪心,对于 个取最小值的组合,尽量用 里较小的和补集里较大的匹配,剩下 同理,这个可以交换证明,但很显然。
然后可以发现会有某些数必须放最小值,有些必须放最大值,这个可以双指针,但我用 set,STL 给我的自信。
int a[N], b[N], n, vis[2 * N], cnt;
void solve() {
cin >> n; rep(i, 1, n) cin >> b[i];
rep(i, 1, n) vis[b[i]] = 1;
int ans1 = n, ans2 = n;
set<int> s;
rep(i, 1, 2 * n) if(!vis[i]) s.insert(i);
rep(i, 1, n) {
if(s.upper_bound(b[i]) == s.end()) break;
ans1--; s.erase(s.upper_bound(b[i]));
}
s.clear();
rep(i, 1, 2 * n) if(!vis[i]) s.insert(i);
req(i, n, 1) {
if(s.lower_bound(b[i]) == s.begin()) break;
ans2--; s.erase(prev(s.lower_bound(b[i])));
}
rep(i, 1, n) vis[b[i]] = 0;
cout << n - ans1 - ans2 + 1 << "\n";
}
E.Plan of Lectures
评价是没有 D 难,注意到 个特殊约束相当于把两个数捆绑在一起,判断矛盾使用带权并查集维护,权值越小表示这个数越靠后。打包好之后相当于进行了一个缩点,按照 的限制建图,最后跑拓扑排序即可。
三种无解的情况 :
- 维护 个约束时出现矛盾,
- 建图时和已有约束矛盾,
- 拓扑排序时有环。
虽然写的很答辩,也调了很久,但很直观(个人而言)。
int n, k;
int a[N], in[N];
struct node {
int id, f, dep, siz; //dep 表示权值,dep越大,说明它在当前节点内应该越靠前
}b[N];
pii tmp[N];
int find(int x) {
if(b[x].f == x) return x;
int p = b[x].f;
b[x].f = find(b[x].f);
b[x].dep += b[p].dep;
return b[x].f;
}
bool mer(int x, int y) {
int xx = find(x), yy = find(y);
if(xx != yy) b[xx].f = yy, b[xx].dep = b[yy].siz, b[yy].siz += b[xx].siz;
xx = find(x), yy = find(y);
if(b[x].dep - b[y].dep != 1) return 0; //合并后权值差不为 1 说明出现矛盾,不满足相邻
return 1;
}
vector<int> ans, p[N], e[N];
void add(int u, int v) {e[u].emplace_back(v);}
void solve() {
cin >> n >> k;
rep(i, 1, n) b[i].f = i, b[i].dep = 0, b[i].id = i, b[i].siz = 1;
rep(i, 1, n) cin >> a[i];
rep(i, 1, k) {
int x, y; cin >> x >> y;
if(!mer(x, y)) {cout << "0\n"; return;}
}
rep(i, 1, n) {
if(!a[i]) continue;
if(find(a[i]) == find(i)) {
if(b[a[i]].dep <= b[i].dep) {cout << "0\n"; return;} // a[i] 已经排在 i 后面,说明无解
continue;
}
add(find(a[i]), find(i)), in[find(i)]++;
}
rep(i, 1, n) tmp[i].fi = b[i].dep, tmp[i].se = i;
sort(tmp + 1, tmp + n + 1, greater<pii>());
rep(i, 1, n) p[find(tmp[i].se)].push_back(tmp[i].se);
// 维护缩点后每个点内的排列顺序
queue<int> q;
rep(i, 1, n) {
if(!in[i] && find(i) == i) {
q.push(i);
}
}
while(q.size()) {
int x = q.front(); q.pop();
if(p[x].size()) ans.push_back(x);
for(int ed : e[x]) {
if(!--in[ed]) q.push(ed);
}
}
rep(i, 1, n) if(in[i]) {cout << "0\n"; return;} //成环无解
for(int i : ans) for(int j : p[i]) cout << j << " ";
}
F.Max Correct Set
3100 的神秘题,写不了一点,skip。
Educational Codeforces Round 101
A.Regular Bracket Sequence
垃圾翻译,原来只有一个左括号和一个右括号,直接 check 开头结尾是否合法即可。
B.Red and Blue
两个序列前缀和最大值加起来。
C.Building a Fence
想清楚式子,维护当前篱笆底边能放的最低的高度和最高的高度,check 下一个能否合法放置即可。
void solve() {
cin >> n >> k;
rep(i, 1, n) cin >> h[i];
int mn = h[1], mx = h[1];
rep(i, 2, n) {
//h[i], h[i] + k - 1, mx + k - 1, mn + 1 - k
if(h[i] > mx + k - 1 or h[i] + 2 * k - 2 < mn) {cout << "NO\n"; return;}
mn = max(h[i], mn + 1 - k);
mx = min(h[i] + k - 1, mx + k - 1);
}
if(mn > h[n]) cout << "NO\n";
else cout << "YES\n";
}
D.Ceil Divisions
直观想法是把 之内的数除以 ,这样剩下一个 , 操作次数 级别,过不去。
发现 开 次根号就变成 ,直接倒序处理,遇到 就把 除一下,这样 除最多 次,可以通过。
void solve() {
int n; cin >> n;
if(n <= 2) {cout << "0\n"; return;}
int now = n;
vector<pii> ans;
req(i, n - 1, 2) {
if(i == (int)sqrt(now)) now = ceil(1.0 * now / i), ans.push_back({n, i});
if(i == 2) {
while(now != 2) now = ceil(1.0 * now / 2), ans.push_back({n, 2});
}
ans.push_back({i, n});
}
cout << ans.size() << "\n";
for(auto [x, y] : ans) cout << x << " " << y << "\n";
}
E.A Bit Similar
很妙。发现直接找符合条件的不好做,反过来考虑,不满足条件的序列是怎样的。
很容易发现,对于 的每个长度为 的子串,按位取反后就是一个不合法的串,一共有 长度为 的子串,最坏情况下(互不相同),就会有 个不能选,设 ,显然当 时一定有解。
考虑如何构造解,设 ,那么前面的 位用不到,可以全部填 ,后面的位共有 种情况, 划窗去掉所有不合法的 ,找到剩下最小的转成二进制输出就是答案,可以开桶,也可以用 set 维护。
坑点不少:
- 由于存在重复的情况, 时也可能有合法的解,不能直接特判掉。
- 前面全部填 的部分也可能产生匹配,注意前面能匹配时这个串就合法了,不能删掉。
int n, k;
string str;
set<int> s;
void solve() {
cin >> n >> k >> str; str = " " + str;
int z = n - k + 1;
int lg = 0; while((1 << lg) <= z) lg++;
lg = min(lg, k);
rep(i, 0, (1 << lg) - 1) s.insert(i);
int now = 0, cnt = 0;
rep(i, 1, k - lg) if(str[i] == '0') cnt++;
req(i, k, k - lg + 1) if(str[i] == '0') now += (1 << k - i);
if(!cnt and s.find(now) != s.end()) s.erase(now);
rep(i, k + 1, n) {
if((now >> lg - 1) & 1) now -= 1 << lg - 1;
if(str[i - k] == '0') cnt--; if(str[i - lg] == '0') cnt++;
now <<= 1;
if(str[i] == '0') now++;
if(!cnt and s.find(now) != s.end()) s.erase(now);
}
if(s.empty()) {
cout << "NO\n";
return;
}
cout << "YES\n";
int t, v = *s.begin();
vector<int> ans; while(v) ans.push_back(v & 1), v >>= 1;
reverse(all(ans));
t = k - ans.size();
rep(i, 1, t) cout << "0"; for(auto i : ans) cout << i;
cout << "\n";
s.clear();
}
写的丑,常数大。
F.Power Sockets
咕咕咕。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】