NOIP 2024 游记 & 赛前训练总结
前面都是比赛前的训练,会含有一些比赛经验。游记写在最后。
day #-18(11.11)
赛时
今天做信友错的模拟赛。
第一题是和最短路有关的,看到 \(n\le 500\) 就想到了 \(n^3\log n\),然而看了很久都不会做,于是果断火速打了 \(O(n^4)\) 的暴力走人,get 50pts。
然后看第二题,发现是最大异或路径,正好最近刚学了线性基,于是想到之前做的一道线性基的题目,把每个环加入线性基。然后就能做到 \(n^2\),再补一个树的性质。get 55pts。此时比赛刚过一小时。
很快啊,其他人还在做 T1,我就开了 T3。看了一会想到用并查集维护,再记录修改前的以实现撤销。发现大样例都过了,此时我还以为复杂度是正确的,此时比赛刚刚过半。但是去了一下洗手间,就想到可以构造一条链卡到平方,这个做法只能过随机的性质和不撤销的性质。于是又补了链的性质。get 55pts。
此时光靠暴力已经得到 160pts 了。
比赛刚过半,看了一下第四题,觉得还是要先补一下第一题的性质,补了一个性质 A,多得了 10pts。
这时我去尝试想中间两道题的正解,无获,于是开始打最后一题的暴力。
后半程有点松弛。
发现暴力分很多啊,在最后 20 分钟写完了 \(n\le 9\) 的 40pts 暴力,然后又补了前两个性质共 8pts。
赛后 & 总结
然后——比赛结束,最后一题 48pts \(\to\) 60pts,最后总分 \(50+55+55+60=230\) 喜提总榜 14,jz 第 3,原来是暴力场。
赛后发现 T1 是线段树分治的思想,还有 Floyd 插入单个点的技巧,我们只以一个点集内的点为中转点转移(插入顺序没有要求),就可以求出只经过(不包括起点终点)这个点集内的点的最短路。
T2 是线性基的结论,\(qmax(i\oplus j)=qmax(i)\oplus qmin(j)\)。
T3 是树剖+线段树的 4K 大数据结构,就是把轻链的贡献全部挂在重链上,有点类似动态 DP 那种。
T4 是论文题,用结论的式子计数。而暴力就是递归,然后在递归过程中进行剪枝就能跑得比较快。
总结:这场比赛时间分配还不错,前面暴力打得比较快,调试也没花多少时间,大概是经验越来越丰富了吧。但可惜后面充裕的时间内没有想出正解,其实正解是不难的,还有提升空间。
改题经验
改 T3 的时候有一个数组没开 long long
,幸好很快就查出来了。
改 T1 的时候在分治时把枚举 \([l,mid]\) 写成 \([1,mid]\) 了。
day #-14(11.15)
今天输了!
20:35 开始打 CF div.2。
顺序开题。
T1 是唐题,T2 是唐题。
T3 我蠢了,1 h 时还没过,于是去写 T4。
T4 也想了不少时间,终于想到一个用 set+BIT 的做法。
T5 是树同构,我还以为要换根于是没写。
T6 是交互,没看。
最后成果:T1+T2+T4。
赛后发现 T3 的正解就是我想的那样,结果 tm 的 T5 的同构是有根树下的,又被骗了。
谴责 CF 的题面跟 shi 一样长。
总结:
- 要有耐心,不要因为前面的题不会,后面就不去想了。
- 不要花太多时间在一道题目上,扔掉后就不要再去想它了。
day #-13(11.16)
今天又做心有错。
T1 简单题。
后面都不会做,把暴力都拿了。
预计:100+60+60+50=260。
实际:72+60+60+20=212。
T1 我自信地认为 \(n^3\times V\) 不会爆 long long
,导致我没有取模。
而 T4 不知道为什么 \(O(n^3)\) 没有拿到第二档分,tm 的 OI 赛制还绑 subtask。
赛后
T2 是对于长度相同的区间的线段树是同构的,运用这个性质记忆化递归。
T3 题面很简单,正解是神奇哈希,被骗啦,哈哈哈,把 shi 题放在 T3 让人以为是科技题。大概是把 \(n\) 个位置用哈希值转化成 \(\sum\) 个字符的不同情况。
T4 是分治,矩形分为跨过分界线和不跨过两种,不跨过是子问题,递归求解;跨过可以暴力做。每次对当前矩形割长的一边,这样复杂度就正确了,时间是 \(T(n)=4T(n/2)+O(n^2)=n^2\log n\)。
总结
- 要有耐心,要充分利用时间。
- 要注意数据范围,记得取模,不要想当然。
- 神奇的题要想想神奇的算法,比如哈希之类的,这又让我想起星战那道奇葩题。
dsy #-9(11.20)
心有错。
赛时
T1 想到二分答案,然后就不会做了,我都要以为是罚坐场了。
前面浪费了一个小时,然后去开 T2,写了一个 \(O(n^2)\) 暴力,对着边权讨论一下,然后终于想到了用线段树模拟最短路的过程,写完已经十一点了。
后面的时间打了剩下三题的暴力,这时感觉 T4 有点像哈希,但是觉得敲不出来了,就没继续想。
赛后
发现 T1 是我不会证的结论(猜结论题),T3 是 SG 函数的规律(打表题),T4 真的是哈希。
之后新学了博弈论相关的知识。
总结
- 这场比赛在第一题浪费太多时间了,应该猜到题目不按难度排列的,一定要先做擅长的题和有想法的题。
day #-4(11.25)
今天的题都挺好的,但就是我没做出来几道,感觉思维不够敏捷了?这一周一定要努力了。
今天还是做心有错,T1 浪费了我一个半小时,T2 已经接近正解了,T3 用了错解结果过了,T4 其实不算难题但我没看。
T1
TAG:线性筛、质因数分解、最优化分解。
这道题很容易想到 \(O(T\dfrac{\sqrt[k]n}{\ln\sqrt[k]n})\) 的做法,就是当 \(k=3\) 时,质数分解到 \(2^{20}\),之后枚举所有质数即可。
然后我就绞尽脑汁没想到正解,但我想到和正解很接近的思路:
发现有很多小的质数对答案没有贡献,那怎么去掉它们呢?
然后我并不知道怎么去掉它们,原来只需要把 \(\sqrt[k+1]n\) 的质数筛掉,剩下的最多是三个质数相乘,这个时候直接把剩下的 \(n\) 开根即可,时间复杂度 \(O(T\sqrt[k+1]n)\),由于 \(k\) 随机所以可过。
细节:我们需要用 powl(n,(long double)1/K)
进行开根,但由于指数是小数,所以还是有精度问题,我们还需要 roundl
进行四舍五入。
T2
TAG:逆序对、二维平面、扫描线。
这题我比赛时想到了:
如果选 \(i,j(i<j)\),那么答案为 \(1+2\sum_{k=i+1}^j [a_i>a_k>a_j]\)。
赛时写了一个严格 \(O(n^2)\) 的做法,就是预处理 \(f_{i,j}\) 表示前 \(i\) 个数中 \(a_i\) 大于 \(j\) 的个数。
赛时还想到放到二维平面上,把每个位置看成一个点 \((i,a_i)\),那么就是求一个矩形内点最多有多少个,要满足矩形左上角和右下角是一个点。
赛时觉得左上角应该是一个上凸包,右下角是一个下凸包,然后时间不够了,也没有再深度思考。
而实际上左上角是前缀最大值,右下角是后缀最小值。
发现我们枚举点对 \((i,j)\) 不好统计,而且点对也是 \(O(n^2)\) 的。
考虑 \((k,a_k)\) 对那些 \((i,j)\) 有贡献。
发现满足条件的 \(i\) 是一个区间 \(j\) 也是一个区间,记它们为 \([l_{1,k},r_{1,k}],[l_{2,k},r_{2,k}]\)。
那么再把它放到二维平面上,就是 \(x=[l_{1,k},r_{1,k}],y=[l_{2,k},r_{2,k}]\) 的一个矩形内的点有 \(+1\) 的贡献。
那么线段树扫描线找到被最多矩形覆盖的点即可。
把一个矩形 \([l_{1,k},r_{1,k}],[l_{2,k},r_{2,k}]\),分为 \((l_{1,k},r_{1,k},l_{2,k},1)\) 和 \((l_{1,k},r_{1,k},r_{2,k}+1,-1)\) 即可。
T3
TAG:最小生成树、Prim、Kruskal、线段树、均摊、启发式合并。
做法 1
考虑 Kruskal。
把点按点权重标号,然后用 vector
和并查集维护每一个连通块内的点与每个点属于哪一个连通块,set
维护当前每个不同的连通块编号。
对于给的边就直接加;对于剩下的边,我们每次尝试向一个不同连通块内的点连边,失败次数是 \(O(m)\) 的,成功次数是 \(O(n)\) 的,于是均摊复杂度就是正确的。
合并 vector
需要启发式合并。
时间复杂度 \(O((n+m)\log n)\)。
考场上想到了均摊,但是没想到如何维护不同的连通块,但是写了一个相似的可以被卡掉的做法,但结果过了。
做法2
考虑 Prim,也就是每次找离连通块内最近的点加入连通块。
可以用线段树维护离连通块的距离与取出一个最近的点。
更新距离时,所有点的更新被给定的边分成了 \(O(m+n)\) 个区间赋 \(a_i\)。
时间复杂度 \(O((n+m)\log n)\)。
T4
发现取带权重心最优。
这题是动态寻找带权重心,用倍增找。然后权值和需要用较复杂的容斥,需要预处理多个树上 DP 数组,同时还需要换根 DP。
用到的算法并不难,一些结论也大概能猜到,就是在计算上的细节比较多。
总结
- 题目难度不一定顺序,不要花太多时间在看起来很简单的题上。
- 正难则反,多从不同角度思考问题。
- 多从学过的算法中找思路和灵感。
- 使用
powl
时,记得用ruondl
,记得用long double
。 - 可以尝试把序列上的统计问题放到二维平面上思考。
- 统计 \(x\) 有多少个 \(y\) 时,可以考虑对于每个 \(y\) 思考它能贡献到哪些 \(x\)。
- 可以用线段树扫描线做最多覆盖问题,用差分做矩形的扫描线。
- 用启发式合并合并
set
,vector
等。 - 最短路、最近点可以考虑放到线段树上优化更新和取点。
- 树上问题多考虑考虑重心或直径,从中寻找性质。
day #-3(11.26)
P1445 [Violet] 樱花
下午做了这一道奇葩的蓝题,我想了一个小时都不会做。
题意大概是给定 \(n,1\le n\le 10^6\),问 \(1/x+1/y=1/(n!)\) 的正整数解的个数。
大概是这样的:
于是可以得到 \(y>n!\),那么设 \(y=n!+k\),\(k\) 是正整数。
带入得
于是对于任意 \(k\) 满足 \(k\mid n!^2\),都有对应的一个 \(x\),也就有对应的一组 \((x,y)\)。
于是答案就是 \(d(n!^2)\),考虑枚举每个质数 \(p\) 的倍数,计算 \(p\) 在每个数中的指数和,记为 \(cnt_p\),答案就是 \(\prod_p(cnt_p+1)\)。
时间复杂度为 \(O(n\ln n)\)。
day #-2(11.27)
今天挂了 90 pts!!!
原因是 T1 在本地过了大样例后没造大数据,也没写对拍,于是最终的代码数组越界,然而测大样例时数组越界在 windows 下还能正常跑!
以后一定要造大数据或者在虚拟机上跑,windows 不可信。
今天做信友队。
T1
TAG:容斥、枚举。
T1 考虑如果两个点集有交,那么交的部分就减去,枚举一个点,再枚举两个有交点集所在的位置,给它们打上一个减去的标记,这部分是 \(O(nmk^2)\)。
然后枚举两个点集计算答案,这部分是 \(O(n^2m^2)\)。
于是时间和空间都是 \(10^8\) 级别。
T2
TAG:随机化、调整法。
sb 出题人把 sb 脑电波题放在 T2,全场没人过。
题目大意:对于一个 \(n*n\) 的方阵,每个格子是 \(0/1\),方案数定义为不经过 \(0\) 每次往下或往右,从左上角走到右下角的方案数。求构造一个方案数为 \(L\) 的 \(n\le 30\) 的方阵。
这道题是随机化,然而 sb 题解并没有证明,代码也很好打,就是调整法,每次贪心地调整最大的,多随机几次初始矩阵就可以通过。
T3
TAG:背包、增量构造、多项式、递推。
这题是好题。
题目大意:给你 \(n\) 行 \(m\) 列数字,每行选一个数,求在所有选择方案中选出的数的中位数的期望。
赛时做法
考虑枚举最后的中位数 \(x\),然后计算中位数为 \(x\) 的方案数,考虑背包设 \(f_{i,j,k}\) 表示前 \(i\) 行选了 \(j\) 个小于 \(x\) 和 \(k\) 个等于 \(x\) 的方案数。
由于 \(k\) 上界的和为 \(O(nm)\) 于是复杂度为 \(O(n^3m)\)。
我真的不懂为什么 \(O(n^4)\) 和 \(O(n^5)\) 都一样只有 20pts,但愿正式赛会好一点。
然后我就想怎么优化,发现这样求方案数的话,不同中位数之间是没有关联的。
考虑求中位数小于等于 \(x\) 的方案数 \(ans_x\),于是等于的就是 \(ans_x-ans_{x-1}\)。
中位数小于等于 \(x\) 的方案数就要求至少有 \(\dfrac {n+1} 2\) 行选的数要小于等于 \(x\),发现这只与每一行小于等于 \(x\) 的数的个数有关。
然后我就不会了,觉得这个东西还是需要背包。
正解
我们考虑可以把背包刻画成多项式的形式,如果我们要求中位数小于等于 \(x\) 的,设 \(cnt_i\)为第 \(i\) 行小于等于 \(x\) 的个数,那么就有 \(ans_x=[x^{\frac{n+1} 2}]F(x) =[x^{\frac{n+1}2}]\prod_{i=1}^n [(m-cnt_i)+cnt_ix]\)。
考虑把所有 \(a\) 排序后依次加入 \(cnt\) 中,这样就能算出每个 \(x\) 的 \(ans_x\)。
考虑加入第 \(i\) 行的 \(a\) 后多项式怎么变化,考虑到有 \(O(nm)\) 个数,我们只需 \(O(n)\) 暴力修改多项式即可。
初始时 \(F(x)=m^n\)。
然后 \(cnt_i\to cnt_i+1\),我们可以先除以再乘上。
先考虑乘上,如果我们乘上 \((a+bx)\),那么设 \(f_i\) 为 \(i\) 次项系数,则
移项后就能得到除法的式子,如果我们除以 \((a+bx)\),则
发现乘法要从大往小做,除法要从小往大做。
T4
TAG:期望、DP、高斯消元。
这道题鉴定为没有使用过高斯消元做期望问题,现在会了。
part 1
先判断无解的,对 \(O(n^2)\) 个点建图,从能出去的点跑搜索看哪些点出不去。
part 2
对所有 \((i,v),|v|\le n\) 的点取出来,建边然后跑高斯消元,时间为 \(O(n^6)\)。
part 3
发现如果 \(v>O(\sqrt n)\) 那么前面已经走了 \(O(n)\) 距离,已经走出去了,于是只需要 \(|v|\le O(\sqrt n)\) 的点。
时间复杂度为 \(O((n\sqrt n)^3)=O(n^{4.5})\)。
part 4
再优化点数,考虑把一个点的期望表示成 \(f_i=C+\sum_j a_{i,j} f_j\),\(C\) 是常数。如果可以那么就能做到 \(O(n^3)\)。
考虑计算从 \((i,0)\) 开始,对于每一个 \(j\) 中间不经过其他 \((x,0)\) 到达 \((j,0)\) 的期望步数、概率,以及直接走出去的期望步数、概率。
这里的期望步数求的是已经乘过概率的,比方说设 \(q_i\) 表示起点走到它的概率,\(c_i\) 表示起点走到它的期望步数(没有乘上 \(q_i\) 的)、我们求的实际上是 \(q_i,C_i(C_i=q_ic_i)\),因为 \(c_i\) 是不好转移的。
而且我们最后算的也是乘上概率后的期望步数和,即 \(C_i\) 的和。
举例:
\(1\to^{\frac 1 2}\to 2,1\to^{\frac 1 2}\to 3\)。
就有:
\(c_2=c_3=1,q_2=q_3=\frac 1 2,C_2=C_3=\frac 1 2\)。
而就有转移,设 \(p_i\) 为走过出边的概率:
发现对于 \(j<i\),一定始终有 \(v<0\),对于 \(j>i\) 一定始终有 \(v>0\),即转移的图是一个 DAG。
于是便可以 DP,这一部分是 \(O(n^3)\)。
最后就表示成了,设 \(C'\) 表示直接走出去的期望步数,\(f_i=\sum_j q_{i,j} f_j+C'+\sum_j C_{i,j}\)。
移项得 \(-f_i+\sum _j q_{i,j}f_j=-C'-\sum_jC_{i,j}\),高斯消元 \(O(n^3)\)。
高斯消元的时候直接把无解的行列全变成 0 即可。
总结
- 一定要写对拍、造大数据、在虚拟机上过编译。
- 背包的转移可以尝试刻画成多项式的形式。
- 期望问题可以高斯消元,高斯消元可以尝试优化点数,可以用 DP 先处理出特殊点之间的关系以尝试优化点数。
day #-1
今天做线下模拟赛,同样的,去实际考场断网做比赛。
今天打的非常好,没有挂分,\(100+100+80+30=310\),获得了前 \(10\) 名。
赛时
今天是顺序开题,先看 T1,想到在上一次的生成树上改,似乎是经典结论:在树上加一条边后会形成一个环,把环上最大的边取走就可以得到新的生成树,我写的是 \(O(n^2\log n+qn)\) 但实际上可以用 \(Prim\) 做到 \(O(n^2+nq)\),评测时也是 800ms 惊险地卡过了。
发现有的人 \(O(n^2\log n+qn\log n)\) 被卡了、还有人 \(4*5000^2\) MLE 了。
T2 很 sb 但还是惊险地过了。发现数据随机,想到是神奇的做法。
一开始想到类似某次 ABC 的 G 题那样,查询枚举子集、修改再枚举子集,数据随机可以做到 \(O(n+3^m)\),但没有优化前景。
然后又打表发现后面的询问的答案都非常小,于是考虑每次从小到大枚举修改的位数,然后发现它跑得飞快,大约 500ms。
其实这个做法和 bfs 搜索是本质相同的,复杂度或许可以证明是正确的,大概就是因为本质是 \(m\) 维空间的最小曼哈顿距离,空间中的点一定会越来越密集,所以跟数据随机并没有什么关系。
打完前题大概是 \(9:30\),比赛是 \(7:30\) 开始的,经过 2h 了,速度中规中矩。
T3 经过我艰辛的打表,终于发现了 \(A\) 性质的充要条件,我也没有证明,但它对了。
获得 80 pts 后其实还剩一个多小时,但我想先去看 T4。
T4 想到网络流,但是并没有这一档分,想着多骗一点,结果打完了发现建模错了,或许根本不能网络流。
只剩半小时了,赶快去写指数级别的暴力,写完后还剩十多分钟了。
后面就是放到虚拟机上过编译了一下。
其实我知道 T3 的正解和 \(A\) 性质仅差一步之遥,但就亏在 T4 把时间浪费在错解上了,或许也是前面的题花的时间过多了。
而且 T3 的表的规律或许只是运气好,其他人都是考虑放到二维平面上考虑然后得到相同的结论。
赛后
T3 就是在 A 性质上扩展一下,还是 DP 考虑最终序列。
考虑除了第一段的每一段的结尾,它不能是一个隐藏的数,否则这一位填 0 以后和它不能区分,而每一段的除了结尾的位置则可以是隐藏的数。
再考虑每一段最大值的限制,它和第一段有关,如果第一段的结尾是一个隐藏的数,那么如果后面没有出现过最大值,那么无法与第一段的结尾为其它数的序列区分。也就是如果第一段的结尾为隐藏的数,那么后面一定要出现最大值。
那么就设 \(f_{i,j,0/1}\) 表示第 \(i\) 位填了 \(j\),有没有出现过最大值。
当 \(j\) 等于 0 时,那么上一个数为一段的结尾,就要求上一个数不能隐藏。
总结
- 写完代码要注意优化常数,看清数据范围。
- 最小生成树可以使用 \(Prim\) 或 \(Kruskal\)。稠密图 \(Prim\) 优为 \(O(n^2)\),稀疏图 \(Kruskal\) 优为 \(O(m\log m+\alpha(n))\)。
\(Prim\) 可以用优先队列做到 \(O(m\log m)\)。 - 打表找规律,但不要找无意义的规律,不要在这方面浪费太多时间。
- 尝试画图,把数值放到二维平面上或许能很快得到结论。
- 求经过若干操作得到最终序列有多少个,一般问什么就从什么方向考虑,即直接求最终序列满足的条件,用 DP 等构造最终序列。
- 写网络流一定要建模正确,不然假了写这么长的网络流就太浪费时间了。