[做题日记#2] Educational Codeforces Round #10

场次链接:Educational Codeforces Round 10


题目质量还是可以的,体验上来说还行?就是最后一题补不动了(好难)。


A.Gabriel and Caterpillar

题目大意:


毛毛虫在离地面 h 1 h_1 h1 的高度,苹果在 h 2 h_2 h2 高度。毛毛虫每天早晨( 10 − 22 10-22 1022 点)每小时上升 a a a 米,每天晚上( 23 − 9 23-9 239 点)每小时下降 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 2in):

  • 如果 i i i 为偶数,满足 a i ≥ a i − 1 a_i \ge a_{i-1} aiai1
  • 如果 i i i 为奇数,满足 a i ≤ a i − 1 a_i \leq a_{i-1} aiai1

如果满足,请输出这个数组,否则输出 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 1n 每个数都出现恰好一次),同时给出 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 mxii+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 rjri 的右端点我们都处理进来了。

现在只需要考虑有多少个 l j l_j lj 满足 l i ≥ l j l_i \ge l_j lilj 即可,那么就是找有多少条线段 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) (AB) 的路径,使得每条边只经过一次,且至少有一条权值为 1 1 1 的边。

解题思路:


可以证明,一个边双连通分量里,我们任取一条边 ( u , v ) (u,v) (u,v),再取一个起点和终点 ( a → b ) (a \rightarrow b) (ab) ,一定可以使得从 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(1in) 的蚂蚁的位置。

解题思路:


好难,不会写,这里只有一些思路。

假设蚂蚁 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';
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬味的橙汁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值