AtCoder Beginner Contest 361 (PARTIAL) Editorial

0. 闲话

这场比赛是在集训的时候拉着几个人一起打的。

最后我们中排名最高的人因为没有将 G 调试出来而问候出题者的家人。

最后 AC 的题目是 ABCDEF,看来复健的效果还可以。

本题解便包含这 \(6\) 道题。

比赛链接在这里

1. Insert / 插入

\(x\) 插入一个长为 \(n\) 的数组 \(a\) 中的第 \(k\) 个位置,得到数组 \(b\),输出 \(b\)

vector 直接模拟即可。

#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
int n, k, x;
vector<int> a;
int main()
{
    scanf("%d%d%d", &n, &k, &x);
    a.assign(n, 0);
    for (auto &i : a)
        scanf("%d", &i);
    a.insert(a.begin() + k, x);
    for (auto &i : a)
        printf("%d ", i);
    putchar('\n');
}

2. Intersection of Cuboids / 长方体体积交

给定两个长方体的坐标,求这两个长方体的体积交是否为正。

若三个维度的范围中有任意一个交集为空,则体积交不为正。否则为正。

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[6], b[6];
int main()
{
    for (auto &i : a)
        scanf("%d", &i);
    for (auto &i : b)
        scanf("%d", &i);
    for (int i = 0; i < 3; i++)
    {
        if (a[i + 3] <= b[i] or a[i] >= b[i + 3])
        {
            puts("No");
            return 0;
        }
    }
    puts("Yes");
}

3. Make Them Narrow / 紧致化

在一个长度为 \(n\) 的数组 \(a\) 中移除 \(k\) 个数,求操作后最小的极差。

显然,在最优策略下,操作后数组一定是排序后的 \(a\) 中的一个子区间。

排序后枚举所有的子区间,求最小值即可。

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10;
int n, k, a[N], res = 2e9;
int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++)
        scanf("%d", a + i);
    sort(a + 1, a + n + 1);
    for (int i = n - k; i <= n; i++)
    {
        res = min(res, a[i] - a[i - (n - k) + 1]);
    }
    printf("%d\n", res);
}

4. Go Stone Puzzle / 石棋

\(n\) 颗两种颜色的石子排成一行。这一行有 \((n+2)\) 个位置。每次可以将相邻的两颗石子移到空位中。求将一种石子排布转换为回到原地另一种石子排布的可行性以及最小步骤。

\(2\le n\le 14\),考虑状压+最短路。

使用两个整数存储目前石子排布状况以及空位位置,然后跑 Dijkstra。时间复杂度 \(O(n2^n\log (n2^n))\)

#include <iostream>
#include <string>
#include <queue>
#include <cstring>
using namespace std;
const int N = 4e5 + 10;
int n, ns, nt, dp[16][N], res = 0x3f3f3f3f;
bool vis[16][N];
string s, t;
struct st
{
    int x, p;
    bool operator<(const st &ele) const
    {
        return dp[p][x] > dp[ele.p][ele.x];
    }
};
priority_queue<st> q;
int main()
{
    memset(dp, 0x3f, sizeof dp);
    cin >> n >> s >> t;
    for (auto &i : s)
    {
        ns <<= 1;
        if (i == 'W')
            ns |= 1;
    }
    for (auto &i : t)
    {
        nt <<= 1;
        if (i == 'W')
            nt |= 1;
    }
    ns <<= 2, nt <<= 2;
    q.push({ns, 0});
    dp[0][ns] = 0;
    while (q.size())
    {
        int tmp = q.top().x, pos = q.top().p;
        q.pop();
        if (vis[pos][tmp])
            continue;
        // printf("%d %d\n", tmp, dp[tmp]);
        vis[pos][tmp] = true;
        for (int i = 0; i < pos - 1; i++)
        {
            int tnt = tmp + (((tmp >> i) & 3) * ((1 << pos) - (1 << i)));
            if (vis[i][tnt] or dp[i][tnt] <= dp[pos][tmp] + 1)
                continue;
            dp[i][tnt] = dp[pos][tmp] + 1;
            q.push({tnt, i});
        }
        for (int i = pos + 2; i <= n; i++)
        {
            int tnt = tmp - (((tmp >> i) & 3) * ((1 << i) - (1 << pos)));
            if (vis[i][tnt] or dp[i][tnt] <= dp[pos][tmp] + 1)
                continue;
            dp[i][tnt] = dp[pos][tmp] + 1;
            q.push({tnt, i});
        }
    }
    printf("%d\n", dp[0][nt] == 0x3f3f3f3f ? -1 : dp[0][nt]);
}

5. Tree and Hamilton Path 2 / 树上汉密尔顿回路 2

有一棵包含 \(n\) 个节点的由带边权双向边连接的树。

求从某一节点出发访问所有节点所需走的最短路径长度。

题目中有“汉密尔顿回路”,考虑回路的性质。

在一棵树上,回路的长度必然为所有边权之和的 \(2\) 倍,与开始节点以及树结构无关。读者自证不难。

而本题要求“访问所有节点”,而非走一条回路,所以考虑可以少走哪一段路。

实际上就是在刚好访问到最后一个节点然后往回走的这一段路。这一段路是一条链。树的直径刚好就是这棵树上最长的链。

所以,答案就是所有边权之和的 \(2\) 倍减去这棵树的直径。

#include <iostream>
#include <vector>
using namespace std;
const int N = 2e5 + 10;
using ll = long long;
ll lnk[N], res[N], ret, tret;
using pii = pair<int, ll>;
vector<pii> road[N];
int n;
void dfs(int x, int f)
{
    ll mx = 0, tmx = 0;
    for (auto &[i, v] : road[x])
    {
        if (i == f)
            continue;
        dfs(i, x);
        lnk[x] = max(lnk[x], lnk[i] + v);
        if (lnk[i] + v > mx)
        {
            tmx = mx, mx = lnk[i] + v;
        }
        else if (lnk[i] + v > tmx)
        {
            tmx = lnk[i] + v;
        }
    }
    res[x] = mx + tmx;
    tret = max(tret, res[x]);
}
int main()
{
    scanf("%d", &n);
    for (int i = 1, x, y, z; i < n; i++)
    {
        scanf("%d%d%d", &x, &y, &z);
        road[x].push_back({y, z});
        ret += z * 2;
        road[y].push_back({x, z});
    }
    dfs(1, 0);
    printf("%lld\n", ret - tret);
}

6. x = a^b / 幂次

相信你注意到了这个二级标题中的链接。

没错。

这 tm 是原题。还是 NOI 春季测试的题。

我亲眼看见旁边的队友把他自己的 AC 代码复制过去然后 AC。

然后我想起来,自己没 AC。

于是我赶忙调集自己的记忆然后想起了一条性质。

尽管 \(1\le n\le 10^{18}\),但 \(\sum_{i=2}^{\sqrt[3]{n}}\log_i n\) 并不会太大。

于是,对于 \(b\ge 3\) 的情况,直接暴力枚举。

对于 \(b=2\) 的情况,其结果严格等于 \(\lfloor\sqrt n\rfloor\)

发现不太好容斥。所以可以在处理 \(b\ge 3\) 的情况时顺便检查枚举到了多少个完全平方数。

并且,在时间允许的情况下,一定要用 sqrtl 代替 sqrt。我因为这个吃了 \(2\) 发罚时。警钟撅烂

#include <iostream>
#include <set>
#include <cmath>
using namespace std;
const int N = 1e5 + 10;
using ll = __int128;
set<ll> mp;
ll n, sq, tmp;
void ff(ll x)
{
    tmp = sqrtl(x);
    if (tmp * tmp == x)
        sq++;
}
int main()
{
    scanf("%lld", &n);
    for (ll i = 2; i * i * i <= n; i++)
    {
        for (ll j = i * i * i; j <= n; j *= i)
        {
            if (mp.count(j))
                continue;
            ff(j);
            mp.insert(j);
        }
    }
    printf("%lld\n", (ll)(floor(sqrtl(n))) - sq + mp.size());
}

结语

原来不只是洛谷在蒸蒸日上。

posted @ 2024-07-08 18:36  丝羽绫华  阅读(7)  评论(0编辑  收藏  举报