AtCoder Beginner Contest 361

A - Insert (abc361 A)

题目大意

给定一个数组\(a\)和数 \(k,x\),将 \(x\)插入第 \(k\)个数之后,并输出新数组。

解题思路

\(vector\)的直接 \(insert\)即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, k, x;
    cin >> n >> k >> x;
    vector<int> a(n);
    for (auto& i : a)
        cin >> i;
    a.insert(a.begin() + k, x);
    for (auto i : a)
        cout << i << ' ';
    cout << '\n';

    return 0;
}



B - Intesection of Cuboids (abc361 B)

题目大意

给定两个立方体,问是否相交。

解题思路

因为立方体都是平行坐标轴摆放的,若相交,则说明在各个维度上的线段都相交。(可以考虑二维的长方形)

因此判断三个维度的线段是否都相交即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

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

    int a, b, c, d, e, f;
    int g, h, i, j, k, l;

    cin >> a >> b >> c >> d >> e >> f;
    cin >> g >> h >> i >> j >> k >> l;

    auto overlap = [](int l1, int r1, int l2, int r2) {
        return max(l1, l2) < min(r1, r2);
    };

    bool x = overlap(a, d, g, j);
    bool y = overlap(b, e, h, k);
    bool z = overlap(c, f, i, l);

    if (x && y && z) {
        cout << "Yes" << endl;
    } else {
        cout << "No" << endl;
    }

    return 0;
}



C - Make Them Narrow (abc361 C)

题目大意

给定\(n\)个数\(a_i\),删去其中的 \(k\)个数,使其极差(最大值与最小值的差)最小。

解题思路

\(a_i\)从小到大排序,我删除其中的 \(k\)个数,一定是从最小值和最大值开始删除,不会从中间删(这对极差不会有任何影响)。

决策就是我从最小值开始删多少个数,如果我选择删 \(x\)个数,那么从最大值开始要删 \(k-x\)个数。所有的 \(x\)的情况的极差取个最小值即为答案。

\(x\)的范围就是 \(O(n)\),直接枚举 \(x\)即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, k;
    cin >> n >> k;
    vector<int> a(n);
    for (auto& x : a)
        cin >> x;
    sort(a.begin(), a.end());
    int ans = 1e9 + 7;
    for (int i = 0; i <= k; i++) {
        ans = min(ans, a[n - (k - i) - 1] - a[i]);
    }
    cout << ans << '\n';

    return 0;
}



D - Go Stone Puzzle (abc361 D)

题目大意

\(n+2\)个格子,其中前 \(n\)个格子有石头,石头有黑有白。每次操作。

将相邻两个石头移动到无石头的位置,俩石头相对顺序不变。

给定初始局面和最终局面,问操作次数的最小值。

解题思路

注意到\(n \leq 14\),局面数最多只有 \(C_{14}^{7} < 1e5\)。因此直接从初始局面进行 \(BFS\),枚举操作,转移后续状态即可。

枚举操作即,先花 \(O(n)\)找到空位,然后花 \(O(n)\)枚举要移动的两个石头,移动后得到后继状态。转移复杂度即为\(O(n)\)

由于数很小,状态记录可以直接用vector,用map记录抵达状态的操作次数,开销不会很大,不用二进制压缩,也方便写转移。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    string s, t;
    cin >> n >> s >> t;
    vector<int> st(n + 2, 2), ed(n + 2, 2);
    for (int i = 0; i < n; i++) {
        st[i] = s[i] == 'B';
        ed[i] = t[i] == 'B';
    }
    map<vector<int>, int> cnt;
    queue<vector<int>> q;
    q.push(st);
    cnt[st] = 0;
    while (!q.empty()) {
        auto u = q.front();
        q.pop();
        int d = cnt[u];
        if (u == ed) {
            break;
        }
        int empty = find(u.begin(), u.end(), 2) - u.begin();
        for (int i = 0; i < n + 1; ++i) {
            auto v = u;
            if (v[i] != 2 && v[i + 1] != 2) {
                swap(v[i], v[empty]);
                swap(v[i + 1], v[empty + 1]);
                if (!cnt.count(v)) {
                    cnt[v] = d + 1;
                    q.push(v);
                }
            }
        }
    }
    if (!cnt.count(ed)) {
        cnt[ed] = -1;
    }
    cout << cnt[ed] << '\n';

    return 0;
}



E - Tree and Hamilton Path 2 (abc361 E)

题目大意

给定一棵树,边有边权。问从一个点出发,访问完所有节点的最小路径长度。

解题思路

首先注意到,从一个点出发\(st\),最终停下来的点一定是叶子\(ed\)

其次,考虑每条边访问的次数,会发现只有\(st \to ed\)这条链上的边只访问了一次,其他边均访问两次。

因此最终的路径长度即为 \(2 \sum e_i - cost(st \to ed)\),即所有边权和的两倍减去根到叶子的距离

最小化路径长度,即最大化根到叶子的距离

由于根不是指定的,因此要找的是树上两点的最长距离

这即为树的直径,两次 \(DFS\)即可找出。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<vector<array<int, 2>>> edge(n);
    LL sum = 0;
    for (int i = 0; i < n - 1; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        --u, --v;
        edge[u].push_back({v, w});
        edge[v].push_back({u, w});
        sum += w;
    }
    vector<LL> dis(n, 0);
    auto dfs = [&](auto&& dfs, int u, int fa) -> void {
        for (auto [v, w] : edge[u]) {
            if (v == fa)
                continue;
            dis[v] = dis[u] + w;
            dfs(dfs, v, u);
        }
    };
    dfs(dfs, 0, 0);
    int l = max_element(dis.begin(), dis.end()) - dis.begin();
    dis.assign(n, 0);
    dfs(dfs, l, l);
    LL max_dis = *max_element(dis.begin(), dis.end());
    cout << sum * 2 - max_dis << endl;

    return 0;
}



F - x = a^b (abc361 F)

题目大意

给定\(n\),求 \(x \in [1,n]\) ,满足存在\(a,b(b \geq 2)\),使得 \(x = a^b\)

\(n \leq 10^{18}\)

解题思路

由于\(b \geq 2\),因此 \(a\)的范围就是 \([1, \sqrt{n}]\),即\(a \in [1, 10^9]\),直接枚举\(a,b\)的话是 \(O(10^9 \log 10^9)\)

注意到当\(b \geq 3\)时,\(a \in [1, 10^6]\) ,因此先枚举\(a \in [1, 10^6]\) ,把\(a \leq 10^6, b \geq 2\)\(x\)全部找出来,这里的时间复杂度为 \(O(10^6 \log 10^6)\),全部存在vector然后排序去重即可得到这部分的\(x\)的数量。

然后考虑剩下的 \(10^6 < a \leq 10^9, b = 2\)\(x\)的数量。

一个比较浅显的想法,认为这部分的数量为\(\lfloor \sqrt{n} \rfloor - 10^6\)。但容易发现会算重:如果\(a = c^2\),其中\(c \leq 10^6\),那么 \(a^2 = c^4\) ,这个数其实是上面算过的(\(a \leq 10^6, b \geq 2\)部分)。

因此要把重复的部分去掉,考虑怎样的\(a\)是重复的,即存在\(b \geq 2, a = c^b(c \leq 10^6)\),容易发现这个条件就是上面枚举计算的条件,即重复的都在vector里出现过。

因此\(\lfloor \sqrt{n} \rfloor - 10^6\)再减去vector里出现过的\((10^6, \lfloor \sqrt{n} \rfloor]\)的数即可,二分找到对应的下标相减即为重复的数量。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    LL n;
    cin >> n;
    int up = 1e6;
    LL upp = 1e18;
    vector<LL> s{1};
    for (int i = 2; i <= up; i++) {
        __int128 x = 1ll * i * i;
        while (x <= n) {
            s.push_back(x);
            x *= i;
        }
    }
    sort(s.begin(), s.end());
    s.erase(unique(s.begin(), s.end()), s.end());
    LL ans = s.size();
    int half = sqrt(n);
    if (half > up) {
        ans += half - up;
        auto r = upper_bound(s.begin(), s.end(), half) - s.begin();
        auto l = upper_bound(s.begin(), s.end(), up) - s.begin();
        ans -= r - l;
    }
    cout << ans << '\n';

    return 0;
}



G - Go Territory (abc361 G)

题目大意

二维平面,有障碍物,可以上下左右走。

问有多少个点,不可以走到\((-1, -1)\)

解题思路

看的时候发觉很久以前做过的类似的题,基本做法一致。

首先看样例给的图

example

一个朴素的想法就是找到不在原点连通块的点,这些点的个数和就是答案。

如果二维平面很小的话,可以对每个点进行\(BFS\),找到所有的连通块,然后累计非原点连通块的点,其值即为答案。

但是这里的平面大小有 \(10^5 \times 10^5\),不能\(BFS\)

由于每个连通块都是一个封闭的图形,我们要统计的就是这个图形的点数,类似面积,可以使用扫描线的方法。从下往上扫描每一行的线段,这些线段是由障碍物作分割,然后用并查集维护这些线段所属的连通块。

考虑第\(i\)行,第 \(i\)行的障碍物把该行分割成了 \(x\)条线段,并且已经用并查集维护好了这些线段所属的连通块。然后考虑第 \(i+1\)行,第 \(i+1\)行的障碍物同样把该行分割成了 \(y\)条线段,现在我们就需要将这两行线段合并,得到第 \(i+1\)行的每个线段所属的连通块是哪个。

合并即考虑上下两行的两个线段,如果它们是相交的,那么它们应属于同一个连通块,并查集合并起来。这是一个模拟的过程,有点小细节。

得到第 \(i+1\)行的线段的连通块关系,继续合并第 \(i+2\)行,依次往复,扫描整个平面。最后遍历所有连通块,把不在原点连通块的点累加即为答案。

而对于该行没有障碍物的,则视为一条线段,如果有连续若干行无障碍物,这我们可以把这若干行压成一行,视为一条线段。最后扫描时按照障碍物的第一维排序扫描。

特殊处理无障碍物的情况。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

struct segg {
    int l, r, id;
};

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

    int n, h = 2e5 + 8, w = 2e5 + 8; // 大平面[1, h] * [1, w]
    cin >> n;
    vector<pair<int, int>> pos(n);

    for (auto& i : pos) {
        cin >> i.first >> i.second;
        i.first += 2;
        i.second += 2; // 原点视为(1, 1)
    }
    sort(pos.begin(), pos.end());

    int cur = 0;
    vector<int> f;
    vector<LL> cnt;
    array<vector<segg>, 2> seg;

    auto findfa = [&](auto& findfa, int x) -> int {
        return f[x] == x ? x : f[x] = findfa(findfa, f[x]);
    };
    auto crossover = [&](segg& a, segg& b) { return a.r >= b.l && a.l <= b.r; };
    auto add_seg = [&](int l, int r, LL cc) {
        int id = f.size();
        f.push_back(id);
        cnt.push_back(cc);
        seg[cur].push_back({l, r, id});
    };

    auto unionn = [&]() { // 将上下两行线段合并
        auto& last_seg = seg[cur ^ 1];
        auto& cur_seg = seg[cur];
        auto last_pt = last_seg.begin();
        for (auto& i : cur_seg) {
            while (true) {
                if (last_pt != last_seg.end() &&
                    crossover(i, *last_pt)) { // 线段相交
                    int fa = findfa(findfa, i.id);
                    int fb = findfa(findfa, last_pt->id);
                    if (fa != fb) {
                        f[fa] = fb;
                        cnt[fb] += cnt[fa];
                    }
                    last_pt = next(last_pt);
                } else if (last_pt == last_seg.end() ||
                           last_pt->l > i.r) { // 形如 cur_pt ..... last_pt
                    if (last_pt != last_seg.begin())
                        last_pt = prev(last_pt);
                    break;
                } else // 形如 last_pt ..... cur_pt
                    last_pt = next(last_pt);
            }
        }
    };

    auto skip_line = [&](int l, int r) { // 空行[l, r],无障碍物
        if (l > r)
            return;
        cur ^= 1;
        seg[cur].clear();
        add_seg(1, w, (r - l + 1ll) * w);
        unionn();
    };

    auto solve_pos = [&](int l, int r) { // 处理该行的所有障碍物pos[l..r]
        if (l > r)
            return;
        cur ^= 1;
        seg[cur].clear();
        int la = 0;
        for (int i = l; i <= r; ++i) {
            if (pos[i].second - la > 1) {
                add_seg(la + 1, pos[i].second - 1, pos[i].second - la - 1);
            }
            la = pos[i].second;
        }
        if (w > la) {
            add_seg(la + 1, w, w - la);
        }
        unionn();
    };

    LL ans = 0;
    if (n == 0) {
        ans = 0;
    } else {
        skip_line(1, pos[0].first - 1);
        int la = 0;
        for (int i = 1; i < n; ++i) {
            if (pos[i].first != pos[la].first) {
                solve_pos(la, i - 1); // 处理同行的所有障碍物
                skip_line(pos[la].first + 1, pos[i].first - 1); // 处理空行
                la = i;
            }
        }
        solve_pos(la, n - 1);
        skip_line(pos[la].first + 1, h); // 最顶部还有空行
        int origin = findfa(findfa, seg[cur].front().id); // 起点的连通块编号
        for (int i = 0; i < f.size(); ++i) {
            if (findfa(findfa, i) == i && i != origin) {
                ans += cnt[i];
            }
        }
    }
    cout << ans << '\n';

    return 0;
}



posted @ 2024-07-06 23:36  ~Lanly~  阅读(365)  评论(0编辑  收藏  举报