【SEOI2024】官方题解

Jefferyz / 2024-06-02 / 原文

【SEOI2024】官方题解

摘要

作为市二编程社举办的第一场公开比赛,主题为数据的处理与分析,赛题主要围绕基本算法以及数据结构展开。难度被设计呈两级分化趋势,\(A\)题与\(B\)题及其简单,\(DEF\)题则在省选难度,毫无疑问\(DEF\)赛时无人拿分,可能是因为时间不足,也有可能是因为选手第一次接受到专业的信息试题,在\(SEOI2025\)我们会对选手进行赛前的简单培训,让选手能够提前熟悉比赛环境从而对难度有更好的适应。

【SEOI2024 A】二元运算器

这题没什么好说的,但是\(Python\)读入会由于行末换行符而多读入一个字符,导致直接进入\(math\ error\)分支或者直接\(RE\),几乎所有\(Python\)选手都因为这点没有拿到第一题的分数。

参考代码:

#include <bits/stdc++.h>

#define I return
#define Love 0
#define Coding ;
using namespace std;

int main() {
    char opt;
    cin >> opt;
    int num1, num2;
    cin >> num1 >> num2;
    if (opt == '+') cout << num1 + num2;
    if (opt == '-') cout << num1 - num2;
    if (opt == '*') cout << num1 * num2;
    if (opt == '^') cout << (int) pow(num1, num2);
    if (opt == '%') {
        if (num2 == 0) printf("math error");
        else cout << num1 % num2;
    }
    if (opt == '/') {
        if (num2 == 0) printf("math error");
        else cout << (int) round(num1 * 1.0 / num2);
    }
    I Love Coding
}

【SEOI2024 B】Grievous

这题是唯一不是我出的题,一开始被排除在题单外,因为这题\(Python\)根本无法使用素数筛,而开小数据会被其他编程语言\(O(n\sqrt n)\)暴力秒掉,而对于\(Python\)而言如果想卡掉这个根号,几乎是不可能的,即使把百万级的数塞进数组就以及无法在最高时限\(5s\)中完成了。在赛前的几个小时,胡同学把这题

加了进来,我紧急把后面大样例删掉了,但这个决定最终被证明是明智的,至少让两支队伍\(A\)掉拿到了\(100\)分,不至于全员爆零。

参考代码:

#include <iostream>
#include <string>
#include <algorithm>

using namespace std;
const int N = 1e8 + 10, M = 2e2 + 10;
int isNotPrime[N];
int Prime[N], cnt, a[N];

int main() {
    int n;
    scanf("%d", &n);
    int maxm = -1;
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]), maxm = max(a[i], maxm);
    for (int i = 2; i <= maxm; ++i) {
        if (!isNotPrime[i]) {
            Prime[++cnt] = i;
        }
        for (int j = 1; j <= cnt; ++j) {
            if (Prime[j] * i > maxm)
                break;
            isNotPrime[Prime[j] * i] = 1;
            if (i % Prime[j] == 0)
                break;
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; ++i)
        if (!isNotPrime[a[i]] && a[i] != 1)
            ++ans;
    printf("%d", ans);
    return 0;
}

【SEOI2024 C】修改向量

本次比赛阴间的开始,没有人进行过尝试,所有提交的人都输出了\(-1\)\(2\)骗了\(5\sim15\)分。

我们记\(\delta = A\cdot B\),如果\(\delta = 0\)显然无需进行修改操作,而对于\(\delta \neq 0\),我们对\(B_i\)进行修改时会对\(\delta\)产生\(|\Delta B\cdot A_i|\)的增减,而单次修改增减的最大值即为\(|b\cdot A_i|\),其中\(b\)为常数,提出得\(\Delta _{max}=|b|\cdot |A_i|\),我们为了最小化修改次数,显然每次因选择最大的\(|A_i|\),于是将\(A\)序列按\(|A|\)大小排序,每次取最大的\(|A|\),能够使\(\delta =0\)时,该次数即为最小操作次数。时间复杂度\(O(n\log_{2}n)\),在于排序。

参考代码:

#include <bits/stdc++.h>

#define int long long
using namespace std;
const int N = 5e5 + 10;
int A[N], B[N];

bool cmp(int a, int b) { return a > b; }

signed main() {
    int flag = 0;
    int n, m;
    scanf("%lld%lld", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%lld", &A[i]);
    for (int i = 1; i <= n; ++i) scanf("%lld", &B[i]);
    int tmp = 0;
    for (int i = 1; i <= n; ++i) tmp += A[i] * B[i];
    if (tmp == 0) {
        puts("0");
        return 0;
    }
    tmp = abs(tmp);
    for (int i = 1; i <= n; ++i) A[i] = abs(A[i]);
    sort(A + 1, A + n + 1, cmp);
    for (int i = 1; i <= n; ++i) {
        if (m * A[i] >= tmp) {
            printf("%lld", i);
            flag = i;
            break;
        } else tmp -= m * A[i];
    }
    if (!flag) puts("-1");
    return 0;
}

【SEOI2024 D】意大利面序列

这题极其考验选手对时间复杂度的优化,对于暴力,显然存在\(O(n^3)\)的算法,枚举左右端点\(O(n^2)\),计算左右端点最大值\(O(n)\)。对于最大值的计算我们可以通过\(ST\)表通过\(O(nlog_{2}n)\),的预处理达到\(O(1)\)查询,总时间复杂度\(O(n^2)\)\(ST\)表写的代码比标算长,赛时有一个人写出来了,但是没编译通过很遗憾。

接下来考虑正解,我们发现\(O(n^3)\)\(ST\)表优化得到的\(O(n^2)\)瓶颈皆在于\(O(n^2)\)的枚举左右端点的,我们对于同一类型的划分寻找相似性,不难发现如果左右端点一处被固定下来的话,一端的最大值也恒定了,此时滑动另一端点,对结果产生影响的只有另一端点在不同位置的最大值。

于是我们尝试构造数列

\[f_i=\sum\limits_{j=1}^{i} max[A_1,A_i] \]

\[g_i=max[A_i,A_n] \]

即前缀\(max\)和,于后缀\(max\),由于确定右端点后右端点贡献恒定为从右端点到序列尾部的最大值,我们只需要对左端点之前每次分割位置得到的最大值做前缀和,最后两者相乘就能得到最终答案。

\[ans=\sum\limits_{i=2}^{n-1} f[i-1]\cdot g[i+1] \]

通过大样例的拟合,我们很容易发现第二问的数学期望逼近\(k^2\),具体数学证明欢迎大家来编程社讨论。

参考代码:

#include <bits/stdc++.h>

#define int long long
const int N = 2e7 + 10;
int a[N], f[N], g[N];
using namespace std;

signed main() {
    int type;
    scanf("%lld", &type);
    if (type) {
        int k;
        scanf("%lld", &k);
        printf("%lld\n", 1ll * k * k);
        return 0;
    }
    int n;
    scanf("%lld", &n);
    int num = (1 + n - 2) * (n - 2) / 2;
    for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
    f[1] = a[1];
    for (int i = 2; i <= n; ++i) f[i] = max(f[i - 1], a[i]);
    for (int i = 1; i <= n; ++i) f[i] += f[i - 1];
    g[n] = a[n];
    for (int i = n - 1; i >= 1; --i) g[i] = max(g[i + 1], a[i]);
    int sum = 0;
    for (int i = 2; i <= n - 1; ++i) sum += 1ll * f[i - 1] * g[i + 1];
    printf("%.9f", sum * 1.0 / num);
    return 0;
}

【SEOI2024 E】旅途的华章

非常有意思的一道思维题,