2023 暑假集训模拟赛 Day 3

CareyBlog / 2023-07-26 / 原文

比赛题目共 \(2\) 套,其中初赛题 \(1\) 套,复赛 \(2\) 题。

比赛时间: \(10:50 - 12:00 a.m\)

Part 0x01 过程-Process

\(8:30\,a.m.\) 做初赛题目;

\(10:40\,a.m.\) 拿到题目;

\(10:41\,a.m.\) 先写 \(\text{T1}\),发现有点像分类讨论;

\(10:50\,a.m.\) 发现 \(\text{T1}\) 不需要那么麻烦,直接取 \(\text{max}\) 即可;

\(11:00\,a.m.\)\(\text{T1}\) 写完了,估计能过,但加了一些暴力写法,防止掉分。

\(11:01\,a.m.\)\(\text{T2}\),并写了个暴力。

\(12:00\,a.m.\) \(\text{T2}\) 排序 \(+\) 去重卡过大样例。

总分 \(= 100pts + 65pts = 165pts\)

Part 0x02 初赛注意事项-Theory

  1. \(∨\) 代表或运算,即 ||
  2. \(∧\) 代表与运算,即 &&
  3. \(?\) 代表非运算,即 !
  4. 文件型病毒传染的主要对象是 EXECOM 文件
  5. Linux下可执行文件没有后缀名
  6. 田忌赛马思想:
    • 对手必胜,就用最小的马输给对手
    • 对手必败,就用最小的马赢对手
    • 我方必胜,就用最小的马赢对手
    • 我方必败,就用最小的马输给对手
  7. 对拍 \(=\) 造数据程序 \(+\) 暴力程序 \(+\) 你的正解
  8. 如果不用补码思想,把 \(n\) 位二进制最高位当符号位,其他位正常储存,那么表示数字范围为 \([-2^n+1, 2^n-1]\),同时会引发 \(0 \neq -0\)\(\text{BUG}\)
  9. \(n\) 位补码中,数字范围为 \([-2^n, 2^n-1]\),同时 \(0 = -0\) ,加减法无需特判,同时也可以根据最高位判断是否是负数。
  10. 浮点数由尾数和阶码构成。
  11. 只给出前序遍历与后序遍历的情况下无法确认二叉树。
  12. 计算机四种存储器:硬盘、\(\text{RAM}\)\(\text{Cache}\)、寄存器,速度逐渐变快,可用空间逐渐变小
  13. 二叉搜索树的每个节点内有数字,满足左子树都小于自己,右子树都大于自己
  14. \(n\) 个物品中有一件次品,已知这件次品轻一些,使用天平分三份称量;
  15. 组合数学从不同角度出发均可得到答案.
  16. \(100\) 以内的质数有 \(25\)
  17. 欧拉筛保证每个数只会被自己的最小质因子筛到一次,所以是 \(O(n)\)
  18. 每过一年星期数 \(+1\),闰年 \(+2\)
  19. 一行组合数加起来是 2 的次幂,也即 \(C(n, 0) + ... + C(n, n) = 2^n\)
  20. 判断 \(\text{T}\) 是否是 \(\text{S}\) 的子序列,可以直接 \(O(|S|)\) 的硬匹配,也可以预处理好 \(S\)Pos[i][j] 数组后 \(O(|T|)\) 地匹配

Part 0x03 复赛题目-Problem

T1 选择乘积

题链

其实不需要分类讨论,直接把可能成为答案的乘积取 max 即可。

为了防止掉分,可以适当的在正解中写暴力

\(\mathfrak{Code\,Here}\)

// g++ "choose.cpp" -Wall -std=c++14 -o "choose"
// ./"choose"

#include <bits/stdc++.h>

using namespace std;

int main()
{
    freopen("choose.in", "r", stdin);
    freopen("choose.out", "w", stdout);

    long long a, b, c, d;

    scanf("%lld %lld %lld %lld", &a, &b, &c, &d);

    if(a >= 0 && c >= 0) //对于前 50% 的数据
    {
        printf("%lld", b * d);
    }
    else if(abs(a) <= 100 && abs(b) <= 100 && abs(c) <= 100 && abs(d) <= 100) //对于前 80% 的数据
    {
        long long ans = -114514;

        for(long long i = a; i <= b; i++)
        {
            for(long long j = c; j <= d; j++)
            {
                ans = max(ans, i * j);
            }
        }

        printf("%lld\n", ans);
    }
    else //正解
    {
        printf("%lld\n", max(max(a * c, a * d), max(b * c, b * d)));
    }

    return 0;
}

时间复杂度 \(O(1)\)

T2 公约数

题链

暴力

考场做法,首先我们意识到,「擦去之后填⼊新的」只是⼀个幌⼦,本质上是要求擦掉一个数之后其他所有数的最大公因数。设删去第 \(i\) 个数后其他所有数的最大公因数为 \(m\),那么只要填⼊ \(m\) 的倍数就能保证公约数最大了。

问题转变为:怎么求删去第 \(i\) 个数后的最大公因数是多少,模拟即可,排序 \(+\) 去重后可通过大样例。

掉了5分,原因:\(n = 1\) 时,可以吧第一个数擦掉,改成题目要求的最大值(\(10^9\)

\(\mathfrak{Code\,Here\,(65pts)}\)

// g++ "gcd.cpp" -Wall -std=c++14 -o "gcd"
// ./"gcd"

#include <bits/stdc++.h>

using namespace std;

int a[100010];
int vis[100010];
int n;

int gcd(int a, int b)
{
    if(b == 0)
    {
        return a;
    }

    return gcd(b, a % b);
}

int getgcd(int x)
{
    int cnt = 0;

    for(int i = 1; i <= n; i++)
    {
        if(i != x)
        {
            cnt = gcd(cnt, a[i]);
        }
    }

    return cnt;
}

int main()
{
    freopen("gcd.in", "r", stdin);
    freopen("gcd.out", "w", stdout);

    scanf("%d", &n);

    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
    }

    sort(a + 1, a + n + 1);
    n = unique(a + 1, a + n + 1) - a - 1;
  
    if(a[1] == 1) //对于另外 20% 的数据
    {
        printf("%d", getgcd(1));
    }
    else if(n <= 10000) //对于前 50% 的数据
    {
        int ans = 0;

        for(int i = 1; i <= n; i++)
        {
            ans = max(ans, getgcd(i));
        }

        printf("%d", ans);
    }
    else
    {
        printf("1");
    }

    return 0;
}

时间复杂度 \(O(n^2)\)

正经做法

\(pre_i = \gcd(a_1, a_2, ···, a_i)\), 有 \(pre_i = \gcd(pre_{i-1}, a_i)\)

\(suf_i = \gcd(a_n, a_{n-1}, ···, a_i)\), 有 \(suf_i = \gcd(suf_{i+1}, a_i)\)

明显删去第 \(i\) 个数后的最大公因数是 \(\gcd(pre_{i-1}, suf_{i+1})\)

\(\mathfrak{Code\,Here}\)

// g++ "gcd.cpp" -Wall -std=c++14 -o "gcd"
// ./"gcd"

#include <bits/stdc++.h>

using namespace std;

int a[100010], pre[100010], suf[100010];

int main()
{
    freopen("gcd.cpp", "r", stdin);
    freopen("gcd.out", "w", stdout);

    int n;

    scanf("%d", &n);

    if(n != 1)
    {
        for(int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
            pre[i] = __gcd(a[i], pre[i-1]);
        }

        for(int i = n; i >= 1; i--)
        {
            suf[i] = __gcd(a[i], suf[i+1]);
        }

        int ans = -114514;
        for(int i = 1; i <= n; i++)
        {
            ans = max(ans, __gcd(pre[i-1], suf[i+1]));
        }

        printf("%d\n", ans);
    }
    else
    {
        printf("1000000000\n");
    }

    return 0;
}

时间复杂度 \(O(n)\)

Part 0x04 总结-Summary

  1. 不要死磕,不要死磕,不要死磕(重要的事情说三遍)
  2. 注意代码细节
  3. 存图应使用邻接表
  4. 当数据小的时候,可以使用 打表 解决问题
  5. 考试注意文件名以及一定要测过了样例,没测样例基本 0 分
  6. 可以自己尝试造数据,需要使用 srand(time(0))rand() 函数
  7. 得到 \([l, r]\) 内的一个整数: rand() % (r - l + 1) + l
  8. 可以多写个暴力来跑自己造出来的数据
  9. 值域过大时不宜用桶,\(10^8\)\(int = 380MB\)
  10. 做题时思路略显粗糙,要加强对于知识点的练习和写模板
  11. \(\gcd\) 具有「可重复贡献性质」,区间 \(\gcd\) 和区间最大值一样需要使用 \(\text{ST}\)
  12. \(\gcd\) 本质上是对指数取 \(\min\)