场次链接:Educational Codeforces Round 10
题目质量还是可以的,体验上来说还行?就是最后一题补不动了(好难)。
A.Gabriel and Caterpillar
题目大意:
毛毛虫在离地面 h 1 h_1 h1 的高度,苹果在 h 2 h_2 h2 高度。毛毛虫每天早晨( 10 − 22 10-22 10−22 点)每小时上升 a a a 米,每天晚上( 23 − 9 23-9 23−9 点)每小时下降 b b b 米(可以下降到地底)。
假设每天 14 14 14 点后观察毛毛虫,问你几天后才能看到毛毛虫在吃苹果,或是说永远吃不到。
解题思路:
根据题意模拟即可。
时间复杂度: O ( h ) O(h) O(h)
AC代码:
void solve() {
int h1, h2, a, b;
cin >> h1 >> h2 >> a >> b;
int day = 0, tot = h1;
tot += a * 8;
while (tot < h2) {
++day;
tot += 12 * (a - b);
if (a <= b) {
cout << -1 << '\n';
return;
}
}
cout << day << '\n';
}
B.z-sort
题目大意:
给出一个长度为 n n n 的数组 a a a。
问你能不能重新排列数组 a a a 使得对于所有的 i i i ( 2 ≤ i ≤ n 2\leq i \leq n 2≤i≤n):
- 如果 i i i 为偶数,满足 a i ≥ a i − 1 a_i \ge a_{i-1} ai≥ai−1 。
- 如果 i i i 为奇数,满足 a i ≤ a i − 1 a_i \leq a_{i-1} ai≤ai−1 。
如果满足,请输出这个数组,否则输出 I m p o s s i b l e Impossible Impossible 。
解题思路:
容易发现我们第一个元素可以放一个最小的,然后根据 大-小-大-小 这样的方式一个个放入数组中,可以发现这样子我们一定会得到一个满足题意的数组。
总的看起来就像是最大的元素和最小小的慢慢向中位数逼近。
时间复杂度: O ( n log n ) O(n \log n) O(nlogn)
AC代码:
void solve() {
int n;
cin >> n;
vector<int> arr(n);
for (auto& v : arr) {
cin >> v;
}
sort(arr.begin(), arr.end());
int i = 1, j = n - 1;
vector<int> ans(1, arr[0]);
while (i <= j) {
if (ans.size() & 1) {
ans.emplace_back(arr[j--]);
} else {
ans.emplace_back(arr[i++]);
}
}
for (auto& v : ans) {
cout << v << " ";
}
}
C.Foe Pairs
题目大意:
给出一个长度为 n n n 的排列( 1 − n 1-n 1−n 每个数都出现恰好一次),同时给出 m m m 个条件 ( a , b ) (a,b) (a,b) ,即 ( a , b ) (a,b) (a,b) 不能同时出现在一个区间里。
问你能选出多少个满足条件的区间。
解题思路:
对于每个下标 i i i 上的 p i p_i pi,我们只需要考虑它能最多向右扩展多少格就行了,那么每个 i i i 作为区间左端点的选法就有 m x i − i + 1 mx_i-i+1 mxi−i+1 种。
时间复杂度: O ( n ) O(n) O(n)
AC代码:
void solve() {
int n, m;
cin >> n >> m;
vector<int> pos(n + 1);
for (int i = 1; i <= n; ++i) {
int x; cin >> x;
pos[x] = i;
}
vector<int> mx(n + 1, n);
for (int i = 1; i <= m; ++i) {
int l, r;
cin >> l >> r;
l = pos[l], r = pos[r];
if (l > r) swap(l, r);
mx[l] = min(mx[l], r - 1);
}
for (int i = n - 1; i >= 1; --i) {
mx[i] = min(mx[i], mx[i + 1]);
}
i64 ans = 0;
for (int i = 1; i <= n; ++i) {
ans += mx[i] - i + 1;
}
cout << ans << '\n';
}
D.Nested Segments
题目大意:
给出 n n n 个端点不重合的线段 ( l , r ) (l,r) (l,r) ,对于每个线段覆盖的区间,问你它包含多少个完整的线段。
解题思路:
我们把线段按照 r r r 来排序,那么显然我们从枚举到第 ( l i , r i ) (l_i,r_i) (li,ri) 时,所有 r j ≤ r i r_j \leq r_i rj≤ri 的右端点我们都处理进来了。
现在只需要考虑有多少个 l j l_j lj 满足 l i ≥ l j l_i \ge l_j li≥lj 即可,那么就是找有多少条线段 l j < l l_j < l lj<l ,然后用当前总线段 i i i 减去即可得到区间内 ( l i , r i ) (l_i,r_i) (li,ri) 包含线段的个数。
这里可以离散化然后用树状数组统计。
时间复杂度: O ( n log n ) O(n \log n) O(nlogn)
AC代码:
template<typename T>
struct BIT {
const int n;
vector<T> tree;
BIT(int n) : n(n), tree(n + 1) {};
T Query(int x) {
T res = 0;
for (int i = x; i > 0; i -= (i & -i)) {
res += tree[i];
}
return res;
}
void Apply(int l, T z) {
if (l <= 0) return;
for (int i = l; i <= n; i += (i & -i)) {
tree[i] += z;
}
}
T rangeQuery(int l, int r) {
return Query(min(n, r)) - Query(max(0, l - 1));
}
};
struct Line {
int l, r, id;
Line(int l, int r, int id) : l(l), r(r), id(id) {}
bool operator<(const Line& rhs) const {
if (r == rhs.r) return l < rhs.l;
return r < rhs.r;
}
};
void solve() {
int n;
cin >> n;
vector<int> hav(1, INT32_MIN);
vector<Line> seg;
for (int i = 0; i < n; ++i) {
int l, r;
cin >> l >> r;
seg.emplace_back(l, r, i);
hav.emplace_back(l - 1);
hav.emplace_back(l);
hav.emplace_back(r);
}
sort(seg.begin(), seg.end());
sort(hav.begin(), hav.end());
hav.erase(unique(hav.begin(), hav.end()), hav.end());
vector<int> ans(n);
BIT<int> fen(hav.size());
for (int i = 0; i < n; ++i) {
auto& [l, r, id] = seg[i];
l = lower_bound(hav.begin(), hav.end(), l) - hav.begin();
ans[id] = i - fen.Query(l - 1);
fen.Apply(l, 1);
}
for (auto& v : ans) {
cout << v << '\n';
}
}
E.Pursuit For Artifacts
题目大意:
给出一个 n n n 点 m m m 边的无向图,每个边的边权为 0 0 0 或 1 1 1 。再给出两个点 A A A 和 B B B。问你这张图中是否存在一条 ( A → B ) (A \rightarrow B) (A→B) 的路径,使得每条边只经过一次,且至少有一条权值为 1 1 1 的边。
解题思路:
可以证明,一个边双连通分量里,我们任取一条边 ( u , v ) (u,v) (u,v),再取一个起点和终点 ( a → b ) (a \rightarrow b) (a→b) ,一定可以使得从 a a a 出发,经过这一条边 ( u , v ) (u,v) (u,v) 然后达到 b b b ,且每条边只经过一次。
当时去问群友有没有证明,然后真有群友用网络流证明出来了。(但是我是fw,我不会网络流,所以在这里我就不证了)
知道了这个结论,这题就好做了。
对原图缩点成边双,然后构建一棵边双树,有以下两种情况:
- 假设缩点后点 u u u 的内部有一条权值为 1 1 1 的边,那么我们只要经过点 u u u 就一定可以经过一条权值为 1 1 1 的边。
- 假设缩点后树边 ( u , v ) (u,v) (u,v) 是一条权值为 1 1 1 的边,那么我们只要从 u u u 到 v v v 就可以经过一条权值为 1 1 1 的边。
构树后 D F S DFS DFS 判断一下上面的情况就好了。
写的有点屎。
时间复杂度: O ( n + m ) O(n+m) O(n+m)
AC代码:
void solve() {
int n, m;
cin >> n >> m;
vector<vector<tuple<int, int, int>>> g(n + 1);
for (int i = 1; i <= m; ++i) {
int u, v, w;
cin >> u >> v >> w;
g[u].emplace_back(v, w, i);
g[v].emplace_back(u, w, i);
}
int A, B;
cin >> A >> B;
int cECC = 0, idx = 0;
vector<int> dfn(n + 1), low(n + 1), bel(n + 1), stk;
vector<vector<int>> scc;
function<void(int, int)> Tarjan = [&](int u, int from) {
dfn[u] = low[u] = ++idx;
stk.emplace_back(u);
for (auto& [v, w, id] : g[u]) {
if (!dfn[v]) {
Tarjan(v, id);
low[u] = min(low[u], low[v]);
} else if (from != id) {
low[u] = min(low[u], dfn[v]);
}
}
if (low[u] == dfn[u]) {
int top = -1;
scc.emplace_back(vector<int>());
while (top != u) {
top = stk.back(); stk.pop_back();
bel[top] = scc.size() - 1;
scc.back().emplace_back(top);
}
}
};
Tarjan(1, 0);
vector<int> mark(scc.size());
vector<vector<PII>> G(scc.size());
for (auto& vec : scc) {
for (auto& u : vec) {
for (auto& [v, w, id] : g[u]) {
if (bel[v] == bel[u]) {
mark[bel[u]] |= w;
} else {
G[bel[u]].emplace_back(bel[v], w);
}
}
}
}
bool ok = false;
function<void(int, int, int)> DFS = [&](int u, int ufa, int check) {
if (u == bel[B]) {
ok |= check;
}
for (auto& [v, w] : G[u]) {
if (v == ufa) continue;
DFS(v, u, check | w | mark[v]);
}
};
DFS(bel[A], bel[A], mark[bel[A]]);
cout << (ok ? "YES" : "NO") << '\n';
}
F.Ants on a Circle
题目大意:
有 n n n 个蚂蚁在一个长度为 m m m 的环上,最初所有蚂蚁位置都不同。它们有的顺时针走,有的逆时针走,且每秒只走长度为 1 1 1 的距离。
当一个蚂蚁遇上了另一个蚂蚁(相撞)时,它们就会改变方向(反弹),从顺时针变成逆时针,逆时针变成顺时针。
给出一个数字 t t t ,问你 t t t 秒后所有编号为 i ( 1 ≤ i ≤ n ) i(1\leq i \leq n) i(1≤i≤n) 的蚂蚁的位置。
解题思路:
好难,不会写,这里只有一些思路。
假设蚂蚁 A A A 撞上了蚂蚁 B B B ,我们固然也可以看成是两个蚂蚁相互穿过,然后互换了编号。
注意到相撞这种情况是不会改变编号的相对位置的,即 1 1 1 一定会是 2 2 2 的前驱,不会出现 2 2 2 是 1 1 1 的前驱的情况。
那么我们只需要固定任意一个蚂蚁,然后模拟它走路的过程,撞到之后交换编号,再通过它来计算剩下蚂蚁的编号和位置即可。
最后就是一些复杂的数学分类讨论了,我不太会写 q w q qwq qwq 。
时间复杂度:大概是 O ( n ) O(n) O(n) 的
WA代码:
void solve() {
cout << "I can't" << '\n';
}