普及+ 模考总结

本篇文章分为一下板块:

  1. 讲解题目/比赛时的一些事情
  2. 赛时分配
  3. 总结

\color{red}{p.s:考试中的模板题目不讲,但是会放算法讲解链接。\\ \ \ \ \ \ \ \ \ \ \ 简单题并不会有很详细的题解,只写了觉得有价值的题目。}

\large{\mathrm{Part}\ \ \mathbf{1} }

挺不错的,这次考试竟然是 IOI 赛制( •̀ ω •́ )y

T1

超级简单的模板题目,打个快速幂然后就……没了!

模板链接:link

模板练习:P1226

T2

一道超级水的小练习,注意到数据范围可用暴力解决,复杂度 O(n^2)

遍历 n 座楼作为落水点,暴力算出最多扩散到哪里即可。

但是,将数据范围调大便会超时。所以,还可以用单调栈,但是并不是模板,而是需要变形。但是,思路与暴力差不多。

T3

这道题目也是 st 表模板。

模板链接:link

模板题目:P3865

T4

这道题目是折半搜索模板。

模板链接:link

模板题目:P1032

T5

这道题目作者没有切调 qwq

死因:复杂度太高,只需要一点点优化就可以 AC,真的不应该。

题解:

网页:link

题目:
screenshotnoscalea9d7e429-e0a7-4b88-85e3-c9114be6f5a2
题目简意:

给定两个包含 n 个的长度为 m 的字符串数组 sp,要从不同的三元组 pos_1,pos_2,pos_3,使得对于 s 数组中任何一个字符串取第 pos_1,pos_2,pos_3 位均与 p 中的不同。请你求出满足要求的三元组的个数。

分析:

注意到数据范围很小,故在比赛的时候,可以考虑复杂度较高的算法,而不要将大量时间花费在想低复杂度算法。

实现步骤:

既然是求三元组可以考虑三重循环,枚举所有的三元组,并且判断是否满足条件,很简单,最重要的是如何写判断函数。

其实判断函数也很简单,我们先便利 s 求出给定在给定的三个位置能组成的所有三元组,再遍历 p 看看有没有重复即可。

代码:
只放判断函数了。

bool check(int pos1,int pos2,int pos3){
    unordered_set<string>sc;//用于记录所有在 s 中的三元组
    for(int o=0;o<n;o++){
        /*计入所有 s 的三元组*/
        string c="";
        c+=s[o][pos1];
        c+=s[o][pos2];
        c+=s[o][pos3];
        sc.insert(c);
    }
    for(int o=0;o<n;o++){
        /*计入 p 的三元组,与 s 的方法一致*/
        if(/*如果在 s 中出现过*/)return 0;
    }
    return 1;
}

复杂度:

时间复杂度为 O(n\times m^3)

T6

这道题目开始想到的是前缀和加上 st 表,但是不管怎么写都是 TLE,考虑到事件原因,就先做下面的题目了。

这道题目一共有三种方法

题目简意:我们需要找到一个区间使得香味值之和大于等于 m ,同时区间内的最大辣度尽可能小。

  1. 简略实现步骤:由于香味值之和需要大于 m,我们可以先让左边界固定,然后找到最小的右端点,使得香味值之和大于等于 m,然后与之前的最小的答案比较即可。不过为了提高效率,我们可以用单调队列优化,即让队列递降,那么队首就是窗口的最大值了。
  2. 简略实现步骤:二分所有的可能辣度值 s,然后判断是否有一个区间,它的最大辣度小于等于 s,并且总香味值大于等于 m。如果是,那么这个辣度值就是当前最佳答案。
  3. 简略实现步骤:与第一种方法一样就是在求最大辣度值的时候用了 st 表而已不讲了。

另外值得一提的是 @邓雅曦 巨佬,赛时与我的思路一样,但是 Ta 只 T 了一个点,这用了侧面描写的手法,写出了本题还有更多优化思路的观点,通过对比,突出巨佬的强大,与作者的弱小,充分凸显了作者还有许多进步空间。

T7

这道题目我是做过的,并且洛谷上一共 3 个版本我都做过,之前在严老师的班级也做过一遍。

题解:

网页版:link

给一个某神奇做法:

我们充分发扬人类智慧,先将所有点按照 x 升序排序再按照 y 升序排序,接着观察题目数据范围,根据前面的排序与数学直觉只要当前的两个点的距离长度大于当前答案,那么就直接退出循环,否则当前答案变为最优答案。这样速度快得飞起,所有数据点都能在 46\mathrm{ms} 内卡过!

接下来给正解方法!分治!

设平面上的点都在点集 S 中。为了将 S 线性分割为大小大致相等的两个子集 S_1S_2,选取一垂直线 l (方程:x = m )作为分割直线,其中 mS 中各点 x 坐标的中位数。由此将 S 分割为:

  • S_1 = \{p \in S | p_x \leq m\}
  • S_2 = \{p \in S | p_x > m\}

这样 S_1S_2 分别位于直线 l 的左侧和右侧,且满足 S = S_1 \cup S_2。由于 mS 中各点 x 坐标值的中位数,所以 S_1S_2 中的点数大致相等。

递归地在 S_1S_2 上求解最接近点对问题,分别得到 S_1S_2 中的最小距离 \delta_1\delta_2,并令 \delta = \min(\delta_1, \delta_2)

S 的最接近点对 (p, q) 之间的距离 d(p, q) < \delta,则 pq 必分属于 S_1S_2。不妨设 p \in S_1q \in S_2,此时 pq 距直线 l 的距离均小于 \delta

P_1P_2 分别表示直线 l 的左边和右边宽为 \delta 的两个垂直长条,则 p \in P_1q \in P_2P_1 中所有点与 P_2 中所有点构成的点对均为最接近点对的候选者。

在最坏情况下有 \frac{n^2}{4} 对这样的候选者,但 P_1P_2 中的点具有稀疏性质:对于 P_1 中任意一点 p,若它与 P_2 中的点 q 构成最接近点对的候选者,则必有 d(p, q) < \delta,满足此条件的 P_2 中的点一定落在一个 \delta \times 2\delta 的矩形 R 中。

由于 P_2 中任何两个 S 中的点的距离都不小于 \delta,可以推出矩形 R 中最多只有 6S 中的点。将矩形 R 的长为 2\delta 的边 3 等分,长为 \delta 的边 2 等分,可得到 6(\frac{\delta}{2}) \times (\frac{2\delta}{3}) 的矩形。

若矩形 R 中存在多于 6S 中的点,必然存在两点 u, v 使得 d(u, v) \leq \frac{5\delta}{6} < \delta,这与 \delta 的定义矛盾。

因此,对于 P_1 中任一点 pP_2 中最多只有 6 个点与它构成最接近点对的候选者。在分治法的合并步骤中,最多只需检查 6\times\frac{n}{2} = 3n 对候选者,而非 \frac{n^2}{4} 对。

虽然知道对于 P_1 中每个点 p 最多只需检查 P_2 中的 6 个点,但不清楚具体是哪 6 个点。可以将 pP_2 中所有 S_2 的点投影到垂直线 l 上。

能与 p 构成最接近点对候选者的 S_2 中点一定在矩形 R 中,它们在直线 l 上的投影点距 pl 上投影点的距离小于 \delta,且这种投影点最多只有 6 个。

若将 P_1P_2 中所有 S 的点按其 y 坐标排好序,对 P_1 中所有点 p,对排好序的点列作一次扫描,就可以找出所有最接近点对的候选者,对 P_1 中每一点最多只要检查 P_2 中排好序的相继 6 个点。这样这道题目就被分治解决了!

T8

这道题目为什么排再最后一题?感觉难度也不是很大呀?

题解:

网页版:link

题目:

screenshotnoscale163beb8a-2ac9-4310-a503-f23ff7ee3230

题目简意:

给你 n 个数,并且任意给定的数都属于 [1,k]

请你求出字典序最小的一组 k 的排列,并且为给定数组的子序列。

分析:
注意到题目的数据范围需要一个小于 O(n^2) 的方法。

考虑到题目要求答案的字典序最小,我们需要用一个容器存储答案,而在实现过程中有可能当前答案并不是最优解,所以这个容器需要容易改动,因此想到用栈的先进后出机制来存储答案。

接下来由于答案需要是 k 的一个排列,所以不能有重复元素,所以我们创建一个容器记录这个元素是否已经计入答案中。

我们还需要再创建一个数组来存储每个型号最后出现的位置,这样防止跳过了它的最后一个位置,还没有将他放入答案里这不是很尴尬,所以我们需要创建这个数组,来看一下跳过当前这个位置后面这个型号还会不会出现。

具体实现步骤:

遍历邦布型号的序列 x,我们对于这个 x 中的每个元素都检查一下他在不在栈里已经出现过,如果出现过的话就直接跳过这个元素进入下一个元素。

还有为了保证答案字典序最小,我们如果当前这个元素大于栈顶的元素的时候,我们就把栈顶元素弹出然后把哈希表里的这个元素也要移除。

最后输出就行了。

代码就不放那么详细了,因为讲解都快把代码放出来了:

    /*输入*/
    /*初始化记录最后出现位置的数组*/
    /*输入,并且记录最后出现的位置*/
    stack<int>st;//最后我们的答案
    unordered_set<int>in;//哈希表
    for(int i=0;i<n;i++){
       if(/*如果这个元素已经被加入答案*/){
            //跳过
        }
        while(/*如果当前元素小于栈顶并且后面还会出现栈顶所代表的元素*/){
            /*删除栈顶,并且在哈希表中删除栈顶所代表的元素*/
        }
        /*将当前元素入栈,并且存入哈希表*/
    }
    vector<int>res;//答案
    while(!st.empty()){
        /*由于栈是先进后出的所以我们还需要把它正过来*/
    }
    /*输出*/

实现复杂度:

由于每个元素都只遍历了一次,故复杂度为 O(n) 可在 1 秒内通过本题。

\large{\mathrm{Part}\ \ \mathbf{2} }

首先看了花了 1 分钟大致看了一下所有的题目,锁定了 T1、T3、T4、T7 为水题,要先解决。

先把 T4 花了 5 分钟秒了,然后跳到 T3 是一道折半搜索板子,大概 6,7 分钟吧,也完了。然后看了 T7 这是一道之前做过的题目,而且做过 4 遍,由于分治的代码浪费时间,打了一个奇葩算法 A 了。

然后跳到 T1,看到题目以为是数学,同余什么的推一推,结果一看数据范围,呀!有两秒!这不打快速幂的可惜,花了大概 2 分钟打了快速幂,切 掉了。

接下来,看其他的题目,T5 看的有点晕,先看了 T6。

T6 一开始的思路是前缀和加上 st 表,但是打出来 TLE 34,想了一下别的思路,没有想出来!?于是又跳回 T5。

注意到数据范围,没多想就打了一个暴力,复杂度是 O(n^2\times m^3) 结果 TLE 80,由于时间不多,去看了 T8。说来也可惜这道题目只要改一下代码就能 AC 的,但是赛时只拿了 80 分,需要反思。

T8 题目没有读懂,问了一下老师,反复读题以后发现,原来题目后面是输出的是补集,而不是输出所选取的集合。

T8 以为很难,读了一下题目,其实让我们求的很简单!比赛最后 4 分钟的时候提交了一遍,结果因为打错了一个字母 CE 了,当时就很急,赶快改了一下,去提交,那个时候真的很紧张里比赛结束还有 23 秒,我很清楚地记得,我 AC 了,真的很惊心动魄。

\large{\mathrm{Part}\ \ \mathbf{3} }

这次比赛,打的不错,但是像 T5 这样的时间上的错误,不能再犯了,这样损失确实很大,这里有一个很常见的技巧,就是一般的是两次循环创建两个数组,再来一个双重循环进行操作,但是很明显可以优化为只有一个循环,然后再加一个循环边创建边比较,这样能省下很多时间,还有像 T6 这样的,前面提到了某位巨佬和我思路一样但是只超时了一个点,这说明,做题还不够多,没有那种一看到题目就知道解法的水平,并且没有想到更多的优化方法,导致严重失分。

打字不易 qwq

3 个赞