CF Educational Round2 个人题解

原来 TOC 是这么用的吗 QwQ

CF Edu Round 2 个人题解

A. Extract Numbers 传送门

题意

把给定字符串用 ,和 ;拆成若干个单词

把其中是非负整数(全为数字,没有前导零除非是"0")的单词用逗号连接成字符串 a

其余单词用逗号连接成字符串 b

然后用双引号包裹,分别输出 a 和 b,空字符串用 - 代替

思路

其实很简单,就是一个模拟,但是考验写法,怎么写能够更加简便呢?

我这里就采用了函数+哨兵的方法,先封装了一个函数用于判断一个字符串是否为数字字符串

然后在遍历字符串的时候如果遇到 , 或 ; 就检查一遍,在最开始的字符串后面加一个 , ,这样就可以做到直接把最后一段字符也收进 a 或 b

输出时忽略最后一个逗号即可,其余见代码
时间复杂度 \(O(n)\)

代码

// A. Extract Numbers
// 2000 ms
// 256 MB
// https://codeforces.com/contest/600/problem/A
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;

int __stt = clock();
int tests = 1;

void solve() {
    std::string s;
    std::cin >> s;
    s += ';';
    auto check = [](const std::string& str) -> bool {
        if (not str.size())
            return false;
        if ((int)str.size() > 1 and str[0] == '0')
            return false;
        for (const auto& c : str)
            if (c < '0' or c > '9')
                return false;
        return true;
    };

    std::string a = "", b = "";

    std::string token = "";
    for (const auto& c : s) {
        if (c == ';' or c == ',') {
            if (check(token)) {
                a += token;
                a += ",";
            } else {
                b += token;
                b += ",";
            }
            token.clear();
        } else {
            token += c;
        }
    }

    auto Print = [](const std::string& outing) {
        if ((int)outing.size() == 0)
            std::cout << "-";
        else
            std::cout << '"' << outing.substr(0, (int)outing.size() - 1) << '"';
    };

    Print(a);
    std::cout << '\n';
    Print(b);
}

int main() {
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    std::cout.tie(0);
    while (tests--) {
        solve();
    }
#ifdef LOCAL
    int __edt = clock();
    std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
    return 0;
}

B. Queries about less or equal elements 传送门

题意

给定一个长度为 \(n\) 的数组 \(A\) 和一个长度为 \(m\) 的数组 \(B\)

问你对于 \(B\) 中的每个元素,\(A\) 中有多少个小于等于 \(B_i\) 的数字

思路

排完序之后查找第一个大于 \(B\) 的元素,前面的元素个数即为答案

这里查找第一个大于 \(B\) 的元素可以用 C++ 自带二分函数 upper_bound 解决

由于二分的,所以复杂度是 $O(m \ log \ n) 的,显然可以过

代码

// B. Queries about less or equal elements
// 2000 ms
// 256 MB
// https://codeforces.com/contest/600/problem/B
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;

int __stt = clock();
int tests = 1;

void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<int> a(n + 1, 0);
    std::vector<int> b(m + 1, 0);

    for (int i = 1; i <= n; ++i)
        std::cin >> a[i];
    for (int i = 1; i <= m; ++i)
        std::cin >> b[i];

    std::sort(a.begin() + 1, a.end());

    for (int i = 1; i <= m; ++i)
        std::cout << (std::upper_bound(a.begin() + 1, a.end(), b[i]) -
                      a.begin() - 1)
                  << " \n"[i == m];
}

int main() {
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    std::cout.tie(0);
    while (tests--) {
        solve();
    }
#ifdef LOCAL
    int __edt = clock();
    std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
    return 0;
}

C. Make Palindrome 传送门

题意

给定一个字符串,你可以任意替换其中的字母为其它的小写字母,然后任意排列这个字符串

问你至少替换多少个字母可以在排列后有办法组成回文串,输出这个字符串,如果有多种方案,输出字典序最小的字符串

思路

显然一个回文串最多只有一个字母会是奇数,同时,若仅有一个字母是奇数,则一定可以组成字符串

也就是说这两个条件是充要的,所以替换最少字母我们只会考虑把出现次数为奇数的字母中的一个替换成出现次数为奇数的字母

接下来考虑怎样可以构造出一个回文串?常见的 trick 是构造回文串的前一半,后一半直接反转。根据这种方式,对于给定的情况,最小字符串构造当且仅当前面一半字符串的字符顺序严格不递减

所以可以推出一个贪心:把 ASCLL 码最大的次数为奇数的字母替换为最小的次数为奇数的一定是优的,因为我们可以让构造方法的前半部分多出一个小字母嘛,要是剩下的奇数字母就放到这个回文串两半的中间连接部分

我们按这个方法就可以写出来一个 \(O(|S|)\) 的代码

代码

// C. Make Palindrome
// 2000 ms
// 256 MB
// https://codeforces.com/contest/600/problem/C
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;

int __stt = clock();
int tests = 1;

void solve() {
    std::string s;
    std::cin >> s;
    std::vector<int> cnt(26, 0);
    for (const auto& c : s)
        ++cnt[c - 'a'];

    std::vector<char> odd;
    for (int i = 0; i < 26; ++i)
        if (cnt[i] bitand 1)
            odd.emplace_back(i + 'a');

    int l = 0, r = (int)odd.size() - 1;
    while (l < r) {
        ++cnt[odd[l] - 'a'];
        --cnt[odd[r] - 'a'];

        ++l;
        --r;
    }

    // for (const auto& num : cnt)
    //     std::cerr << num << ' ';
    // std::cerr << '\n';

    std::string ans = "";
    for (int i = 0; i < 26; ++i)
        if (cnt[i] >= 2) {
            // std::cerr << (i + 'a') << ' ';
            // std::cerr << ((char)(i + 'a')) * (cnt[i] >> 1) << ' ';
            for (int j = 0; j < (cnt[i] >> 1); ++j)
                ans += (char)(i + 'a');
        }
    // std::cerr << (int)ans << '\n';

    if ((int)odd.size() bitand 1)
        ans += odd[(int)odd.size() >> 1];

    std::cout << ans;
    std::reverse(ans.begin(), ans.end());
    if ((int)odd.size() bitand 1)
        std::cout << ans.substr(1) << '\n';
    else
        std::cout << ans << '\n';
}

int main() {
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    std::cout.tie(0);
    while (tests--) {
        solve();
    }
#ifdef LOCAL
    int __edt = clock();
    std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
    return 0;
}

D. Area of Two Circles' Intersection 传送门

题意

给两个圆的圆心和半径,求这两个圆的面积并

思路

这好像是一个板子,但由于我不会平面几何,所以简单贺了个板子,等基础补完了再学

代码

// D. Area of Two Circles' Intersection
// 2000 ms
// 256 MB
// https://codeforces.com/contest/600/problem/D
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;

int __stt = clock();
int tests = 1;

void solve() {
    struct pt {
        real x, y;
    };
    struct cr {
        pt o;
        real r;
    };

    const real eps = 1e-18L;
    const real pi = std::acos(-1.0L);

    auto sgn = [&](real v) {
        if (std::fabs(v) < eps)
            return 0;
        return v < 0 ? -1 : 1;
    };

    auto ddot = [&](pt a, pt b) { return a.x * b.x + a.y * b.y; };
    auto lens = [&](pt v) { return std::sqrt(ddot(v, v)); };

    auto area = [&](cr c1, cr c2) {
        real d = lens({c1.o.x - c2.o.x, c1.o.y - c2.o.y});
        if (sgn(d - (c1.r + c2.r)) >= 0)
            return static_cast<real>(0);
        if (sgn(d - std::fabs(c1.r - c2.r)) <= 0) {
            real mn = c1.r < c2.r ? c1.r : c2.r;
            return mn * mn * pi;
        }
        real x = (d * d + c1.r * c1.r - c2.r * c2.r) / (2.0L * d);
        real t1 = std::acos(x / c1.r), t2 = std::acos((d - x) / c2.r);
        return c1.r * c1.r * t1 + c2.r * c2.r * t2 -
               (c1.r * c1.r * std::sin(2.0L * t1) +
                c2.r * c2.r * std::sin(2.0L * t2)) *
                   0.5L;
    };

    cr cA, cB;
    std::cin >> cA.o.x >> cA.o.y >> cA.r;
    std::cin >> cB.o.x >> cB.o.y >> cB.r;
    std::cout << std::fixed << std::setprecision(30) << area(cA, cB) << '\n';
}

int main() {
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);
    std::cout.tie(0);
    while (tests--) {
        solve();
    }
#ifdef LOCAL
    int __edt = clock();
    std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
    return 0;
}

E. Lomsat gelral 传送门

题意

给定一个 \(n\) 个节点的以 \(1\) 为顶点的有根树,每个节点有一个颜色

对于每一个节点,求出以它为根的子树内出现最多的颜色的颜色编号和

思路

听说这道题可以轻重链剖分然后树上启发式合并,但我不太会,所以这里我写了一个线段树合并

用线段树记录这个区间内颜色出现的最大次数、对应的颜色值、所有出现次数最大的颜色值的和

然后就从根节点开始 DFS 到叶子节点记录线段树,然后层层递推进行线段树合并

需要注意这道题目如果开正常的线段树会 MLE 到起飞,所以需要动态开点线段树。时空复杂度都是 \(O(n \ log \ C) (C 为颜色编号范围)\),较为卡空间,但我 vector 都过了那还说啥呢

代码

// E. Lomsat gelral
// 2000 ms
// 256 MB
// https://codeforces.com/contest/600/problem/E
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;

int __stt = clock();
int tests = 1;

void solve() {
    static constexpr int mxn = 1e5;
    struct nd {
        i64 l, r, s, v, a;
    };
    i64 n;
    std::cin >> n;

    std::vector<nd> t(5000050);
    std::vector<i64> rt(n + 1), clr(n + 1), ans(n + 1);
    std::vector<std::vector<i64>> adj(n + 1);

    i64 cn = 0;

    auto pushup = [&](i64 x) -> void {
        i64 ls = t[x].l, rs = t[x].r;
        if (t[ls].s > t[rs].s) {
            t[x].s = t[ls].s;
            t[x].v = t[ls].v;
            t[x].a = t[ls].a;
        } else if (t[rs].s > t[ls].s) {
            t[x].s = t[rs].s;
            t[x].v = t[rs].v;
            t[x].a = t[rs].a;
        } else {
            t[x].s = t[ls].s;
            t[x].v = t[ls].v;
            t[x].a = t[ls].a + t[rs].a;
        }
    };

    auto update = [&](auto&& update, i64& x, i64 l, i64 r, i64 p,
                      i64 val) -> void {
        if (x == 0)
            x = ++cn;
        if (l == r) {
            t[x].v = l;
            t[x].s += val;
            t[x].a = l;
            return;
        }
        i64 m = (l + r) >> 1;
        if (p <= m)
            update(update, t[x].l, l, m, p, val);
        else
            update(update, t[x].r, m + 1, r, p, val);
        pushup(x);
    };

    auto merge = [&](auto&& merge, i64 a, i64 b, i64 l, i64 r) -> i64 {
        if (a == 0)
            return b;
        if (b == 0)
            return a;
        if (l == r) {
            t[a].v = l;
            t[a].s += t[b].s;
            t[a].a = l;
            return a;
        }
        i64 m = (l + r) >> 1;
        t[a].l = merge(merge, t[a].l, t[b].l, l, m);
        t[a].r = merge(merge, t[a].r, t[b].r, m + 1, r);
        pushup(a);
        return a;
    };

    for (i64 i = 1; i <= n; ++i) {
        std::cin >> clr[i];
        rt[i] = i;
        ++cn;
    }

    for (i64 i = 1; i < n; ++i) {
        i64 u, v;
        std::cin >> u >> v;
        adj[u].emplace_back(v);
        adj[v].emplace_back(u);
    }

    auto dfs = [&](auto&& dfs, i64 x, i64 f) -> void {
        for (auto y : adj[x]) {
            if (y == f)
                continue;
            dfs(dfs, y, x);
            rt[x] = merge(merge, rt[x], rt[y], 1, mxn);
        }
        update(update, rt[x], 1, mxn, clr[x], 1);
        ans[x] = t[rt[x]].a;
    };

    dfs(dfs, 1, 0);

    for (i64 i = 1; i <= n; ++i)
        std::cout << ans[i] << " \n"[i == n];
}

int main() {
    std::ios::sync_with_stdio(0);
    std::cout.tie(0);
    std::cin.tie(0);

    while (tests--) {
        solve();
    }
#ifdef LOCAL
    int __edt = clock();
    std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
    return 0;
}

F. Edge coloring of bipartite graph 传送门

题意

给定一个二分图,要求给这个图进行染色使得相同颜色的边不会在相同的节点连接,即相同的颜色的边不连接

问最少需要多少个颜色,并给出一种构造方案

思路

这道题需要一个较为 tricky 的结论:

Kőnig 定理 二分图边最小染色数 = 二分图最大节点度数

(二分图真是有很多有趣的性质呢QwQ)

证明

我们考虑一个依次加边的过程

对于一条边 \((u, v)\), 它一定过左右两部,若左部节点已有的颜色集合为 \(\mathbb{S_1}\),右部节点已有的颜色集合为 \(\mathbb{S_2}\)

我们这么构造,如果 \(\mathbb{S_1}\)\(\mathbb{S_2}\) 的 MEX 相同,则直接用这个没有被使用过的 MEX 作为该边颜色

否则,我们记 \(A\)\(\mathbb{S_1}\) 的 MEX, \(B\)\(\mathbb{S_2}\) 的 MEX(这里为方便,令 \(A\) < \(B\)\(u\) 在左部点), 那么就会存在一条从 \(v\) 开始的 \(A\), \(B\) 交替的增广路,如果在这条增广路中有一条边包含了 \(u\), 那么由于二分图的特殊性,可以知道该边颜色一定为 \(A\),这与 MEX 相悖

那么此时我们就可以把 \((u,v)\) 钦定为 \(A\),然后沿着这条增广路让边的相邻两点交换颜色

每条增广路最多的长度量级为 \(O(n)\), 有 \(m\) 条边,所以复杂度为 \(O(nm)\),但是这个事实上是卡不满的,要是实在害怕,也可以转化成 netflow 解决,但事实上我写了个 \(O(nm)\) 跑得飞快

代码

// F. Edge coloring of bipartite graph
// 1000 ms
// 256 MB
// https://codeforces.com/contest/600/problem/F
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;

int __stt = clock();
int tests = 1;

void solve() {
    int A, B, M;
    std::cin >> A >> B >> M;

    int n = A + B;
    std::vector<int> u(M), v(M), deg(n + 1);

    for (int i = 0; i < M; ++i) {
        std::cin >> u[i] >> v[i];
        v[i] += A;
        ++deg[u[i]];
        ++deg[v[i]];
    }

    int mx = 0;
    for (int i = 1; i <= n; ++i)
        if (deg[i] > mx)
            mx = deg[i];

    std::vector<std::vector<int>> clr(n + 1, std::vector<int>(mx + 1, 0));
    std::vector<std::vector<int>> col = clr;

    for (int i = 0; i < M; ++i) {
        int p = u[i], q = v[i];
        int c1 = 1, c2 = 1;
        while (c1 <= mx and col[p][c1] != 0)
            ++c1;
        while (c2 <= mx and col[q][c2] != 0)
            ++c2;
        col[p][c1] = q;
        col[q][c2] = p;
        if (c1 == c2)
            continue;
        int cur = c2, nd = q;
        while (nd != 0) {
            col[nd][c1] ^= col[nd][c2] ^= col[nd][c1] ^= col[nd][c2];
            int nx = col[nd][cur];
            cur ^= c1 ^ c2;
            nd = nx;
        }
    }

    std::cout << mx << '\n';
    for (int i = 0; i < M; ++i) {
        int p = u[i], q = v[i];
        for (int k = 1; k <= mx; ++k) {
            if (col[p][k] == q) {
                std::cout << k << " \n"[i == M - 1];
                break;
            }
        }
    }
}

int main() {
    std::ios::sync_with_stdio(0);
    std::cout.tie(0);
    std::cin.tie(0);
    while (tests--) {
        solve();
    }
#ifdef LOCAL
    int __edt = clock();
    std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
    return 0;
}
posted @ 2026-01-25 15:19  Billlly  阅读(0)  评论(0)    收藏  举报