AtCoder Beginner Contest 250 解题报告

这里是时隔 \(n\) 个月的解题报告,由于 A 至 C 题过于简单,本解题报告从 D 题开始。


D

Description

若一个正整数 \(k\) 满足 \(k = p \times q^3\)(其中 \(p, q\) 均为质数且 \(p < q\)),则称 \(k\) 和 250 相似。

问对于给定的 \(n\),不超过 \(n\) 的正整数中有多少是和 250 相似。多组数据。

数据范围:\(1 \le n \le 10^{18}\).

Solution

注意到 \(k = p \times q^3\) 中有 \(q^3\),从而不难发现当 \(1 \le n \le 10^{18}\) 时,一个和 250 相似的数中 \(q\) 只能为不超过 \(10^6\) 的质数。

考虑到我们枚举 \(p\) 时只需枚举到 \(\dfrac{n}{q^3}\),所以这个算法应该会跑的很快,但是由于笔者能力不足,不会分析此算法的时间复杂度(实际上是没有仔细想,不过实际可以通过本题)

Code

Code
/*
* @author Nickel_Angel (1239004072@qq.com)
* @copyright Copyright (c) 2022
*/

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <vector>

using std::min;
using std::max;
using std::sort;
using std::swap;
using std::vector;
using std::pair;

typedef long long ll;

const int maxn = 1e6 + 10;
ll n;
int prime[maxn], tot;
bool vis[maxn];

inline void Euler(int n)
{
    for (int i = 2; i <= n; ++i)
    {
        if (!vis[i])
            prime[++tot] = i;
        for (int j = 1; j <= tot && prime[j] <= n / i; ++j)
        {
            vis[i * prime[j]] = true;
            if (i % prime[j] == 0)
                break;
        }
    }
}

int main()
{
    Euler(1e6);
    scanf("%lld", &n);
    ll q, ans = 0;
    for (int i = 2; i <= tot; ++i)
    {
        q = 1ll * prime[i] * prime[i] * prime[i];
        for (int j = 1; j < i; ++j)
        {
            if (q * prime[j] > n)
                break;
            ++ans;
        }
    }
    printf("%lld\n", ans);
    return 0;
}

E

Description

给定两个长度为 \(n\) 的数组 \(\{a_n\}, \{b_n\}\)

现在给定 \(q\) 组询问,每组询问形如:由 \(\{a_n\}\) 的前 \(x\) 项组成的集合与 \(\{b_n\}\) 的前 \(y\) 项组成的集合是否相等?如果相等,输出 Yes,否则输出 No.

数据范围:\(1 \le n, q \le 2 \times 10^5, 1 \le a_i, b_i \le 10^9\).

Solution

\(firB_x\) 表明 \(x\)\(\{b_n\}\) 中第一次出现的位置,如果 \(x\)\(\{b_n\}\) 中未出现,则为 \(+\infty\)\(firA_x\) 同理。

考虑对于一个特定的 \(x\)。由 \(a_1, a_2, \cdots, a_x\) 组成的集合,设 \(Y = \max_{i = 1}^{x} \limits firB_{a_i}, X = \max_{i = 1}^{y} \limits firA_{b_i}\),则对于询问 \((x, y)\),当且仅当 \(X \le y\)\(Y \le x\)

故直接考虑求出 \(firA, firB\) 的前缀最大值,就可以做到 \(O(1)\) 回答询问。

Code
/*
* @author Nickel_Angel (1239004072@qq.com)
* @copyright Copyright (c) 2022
*/

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <vector>
#include <map>
#include <queue>

using std::min;
using std::max;
using std::sort;
using std::swap;
using std::vector;
using std::pair;

typedef long long ll;

const int maxn = 2e5 + 10;
int n, m, a[maxn], b[maxn], ansA[maxn], ansB[maxn];
std::map<int, int> firA, firB;
std::priority_queue<int> q;

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
    {
        scanf("%d", a + i);
        if (!firA.count(a[i]))
            firA[a[i]] = i;
    }
    for (int i = 1; i <= n; ++i)
    {
        scanf("%d", b + i);
        if (!firB.count(b[i]))
            firB[b[i]] = i;
    }
    for (int i = 1; i <= n; ++i)
    {
        if (!firB.count(a[i]))
            q.push(n + 1);
        else
            q.push(firB[a[i]]);
        ansA[i] = q.top();
    }
    while (!q.empty())
        q.pop();
    for (int i = 1; i <= n; ++i)
    {
        if (!firA.count(b[i]))
            q.push(n + 1);
        else
            q.push(firA[b[i]]);
        ansB[i] = q.top();
    }
    scanf("%d", &m);
    int x, y;
    while (m--)
    {
        scanf("%d%d", &x, &y);
        puts(ansA[x] <= y && ansB[y] <= x ? "Yes" : "No");
    }
    return 0;
}

F

Description

给定由 \(n\) 个整点 \((x_i, y_i)\) 组成的凸多边形,现在要选择两个非相邻的顶点,将多边形按照两个顶点的连线切成两半,然后可以任意选择一半多边形。

现在希望切一刀后选择的一半多边形的面积 \(b\),尽可能接近整个多边形的面积的 \(\dfrac{1}{4}\)(设为 \(a\))。即最小化 \(\left\vert a - b \right\vert\),最终输出 \(8 \times \left\vert a - b \right\vert\),可以证明其恒为整数。

数据范围:\(4 \le n \le 10^5, \left\vert x_i \right\vert, \left\vert y_i \right\vert \le 10^4\).

Solution

由计算几何基础,我们可以得出一个 \(n\) 边形的面积为其各相邻边叉积的和的 \(\dfrac{1}{2}\)

所以我们可以考虑双指针,即对于一个给定的 \((x_l, y_l)\),考虑由 \(\{(x_l, y_l), (x_{l + 1}, y_{l + 1}), \cdots, (x_r, y_r)\}\) 顶点组成的多边形,我们不断移动 \(r\) 指针,使得这个多边形的面积 \(S\) 刚好不超过 \(a\)

我们只需考虑计算出所有 \(l\) 的答案后取最小的即可。

Code
/*
* @author Nickel_Angel (1239004072@qq.com)
* @copyright Copyright (c) 2022
*/

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>

typedef long long ll;

const int maxn = 2e5 + 10;
int n, x[maxn], y[maxn];
ll s[maxn];

inline ll cross(ll a, ll b, ll c, ll d)
{
    return a * d - b * c;
}

inline ll calc(int l, int r)
{
    return s[r] - s[l] + cross(x[r], y[r], x[l], y[l]);
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; ++i)
    {
        scanf("%d%d", x + i, y + i);
        x[i + n] = x[i], y[i + n] = y[i];
    }
    for (int i = 0; i <= 2 * n; ++i)
        s[i + 1] = s[i] + cross(x[i], y[i], x[i + 1], y[i + 1]);
    ll ans = 7e18;
    int r = 0;
    for (int l = 0; l < n; ++l)
    {
        while (calc(l, r + 1) * 4 <= s[n])
            ++r;
        ans = std::min(ans, llabs(s[n] - calc(l, r) * 4));
        ans = std::min(ans, llabs(s[n] - calc(l, r + 1) * 4));
    }
    printf("%lld\n", ans);
    return 0;
}

G

Description

已知接下来 \(n\) 天的股票价格 \(p_1, p_2, \cdots, p_n\),每天你要么买进一股股票,要么卖出一股股票,要么什么也不做。\(n\) 天之后你拥有的股票应为 0,当然,希望这 \(n\) 天内能够赚足够多的钱。

数据范围:\(1 \le n \le 2 \times 10^5, 1 \le p_i \le 10^9\).

Solution

本题为原题 CF865D,是一道反悔贪心题目。

我们考虑一个贪心策略,即我们在股票价格低的时候买入股票,在股票价格高的时候卖出股票。但是这样简单的贪心会出现问题,即如果选择在一天卖出股票,可能在后面会遇到股票价格更高的情况,所以我们考虑在贪心中加入反悔机制。

考虑以下反悔机制:我们在第 \(i\) 天买入了一股股票,在第 \(j\) 天卖出,但是我们后面发现第 \(k\) 天卖出更好,我们将其改为第 \(k\) 天卖出,这时我们的收益为 \(p_k - p_i\)。我们发现其相当于在第 \(i\) 天买入一股后在第 \(j\) 天卖出,又在第 \(j\) 天买入一股,在第 \(k\) 天卖出,这个过程中我们的总收益 \(p_j - p_i + p_k - p_j\) 无疑与前面直接改到第 \(k\) 天卖出的收益是相同的。

我们考虑使用小根堆维护前 \(i\) 天我们可以买入一股股票的最小价格 \(p_{min}\)。当 \(p_i > p_{min}\) 时,我们就可以直接将 \(p_i - p_{min}\) 计入答案,然后将 \(p_i\) 加入堆中,表明我们在第 \(i\) 天卖出一股,又为了后面可能的反悔操作,还可以第 \(i\) 天在买入一股。

Code
/*
* @author Nickel_Angel (1239004072@qq.com)
* @copyright Copyright (c) 2022
*/

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>

using std::min;
using std::max;
using std::sort;
using std::swap;
using std::vector;
using std::pair;

typedef long long ll;

const int maxn = 2e5 + 10;
int n, a[maxn];
std::priority_queue<int, vector<int>, std::greater<int>> q;

int main()
{
    scanf("%d", &n);
    ll ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        scanf("%d", a + i);
        if (!q.empty() && q.top() < a[i])
        {
            ans += a[i] - q.top();
            q.pop();
            q.push(a[i]);
        }
        q.push(a[i]);
    }
    printf("%lld\n", ans);
    return 0;
}

Ex

Description

给定一个 \(n\) 个点 \(m\) 条边的无向图,其中经过结点 \(a_i\) 到结点 \(b_i\) 的边需要时间 \(c_i\)。称标号为 \(1, 2, \cdots, k\) 的点为关键点,现在给出 \(q\) 组询问,每组询问形如高桥君想要从 \(x\) 号点走到 \(y\) 号点(保证 \(x, y\) 均为关键点),如果他在上次休息之后持续走了超过 \(t\) 个单位时间,那么他就不能再移动,他在路程中只可以在关键点休息,问是否存在一种路线,使得高桥君可以成功到达 \(y\) 号点。

数据范围:\(2 \le k \le n \le 2 \times 10^5, n - 1 \le m \le 2 \times 10^5, 1 \le c_i \le 10^9, 1 \le q \le 2 \times 10^5, 1 \le t \le 10^{15}\),保证 \(q\) 组询问的 \(t\) 是不减的。

Solution

(似乎写了一个伪证,待验证)

posted @ 2022-05-11 19:31  Nickel_Angel  阅读(77)  评论(0编辑  收藏  举报