2024牛客多校训练营复盘:上篇(1~5)
牛客多校总帖子链接:https://ac.nowcoder.com/discuss/1295959
第一场:
名次:31
赛时:A B C H I J (6题)
赛后:D
A 题和 B 题是捆绑的,同样的背景,开场过完签到后发现 A 过了好多人。
一开始设计了个 dp,好像是 n 方的,因为题目条件非常简单,队友在想能不能优化。我感觉这题没必要去想太复杂吧,就又仔细画了画,发现存在子序列的AND和,其实就是相当于全选嘛,选的越多AND越小,枚举多少数第一位是1。n方的题,但是好像核心部分是O(n)的,模数不一定是质数使得算组合数成了n^2递推。
B 题变成了存在至少两个,也就是说可以删一个数也能使AND为0,一开始zxn想枚举那个删掉的数的位置,我觉得这不是个能容斥的东西,事实上也确实不能枚举删掉的数是哪个,因为它没有任何位置特征,随便塞在哪里都行。但是zxn又想到了一个妙妙的东西,就是我们 A 题已经做出来了至少一个的,那么减去只有一个的,就是答案了呗,虽然现在看来比较显然,但是当时能想到这个还是挺妙的,毕竟没有 A 题铺垫直接想 B 可能要想很久。
只有一个说明每个数都不能删,都很重要,每个数都有自己独特位置的0,我们把所有m-1个位置的0分给那些数,但是这样会出现一个情况就是某些位置上有好几个0,分配时该如何分呢。经过讨论,发现可以枚举哪些列是只有一个0,其他列至少要有两个0,至少两个0的列可以随便填,只有一个0的列要覆盖到所有行,也就是有序号的盘子放有序号的球的方案。
这就是个经典斯特林数的变种,虽然自己有点记不清那几个斯特林数的具体定义了,不过这题随机模数以及n^2复杂度告诉我们只要写出递推式即可。以后我会再抽时间把斯特林数笔记补齐。
然后到了令人头大的环节,一开始写的n^2log T了,于是预处理了一下变成了n^2,还是T,有点气不过牛客的评测机(我们学校有些队在后面的比赛也被卡常了)。后来把循环次数减小了,勉强卡进,不过WA了,一开始以为是改多了,直到最后半小时才发现m=1特判错了,我们钦定的m-1个位置0分配,是至少要有0的,不然不对。
赛后牛客群里有人讨论到卡常的问题,大佬发来了一个玄学且有效的优化,我暂且称为取模优化:
double dp;
inline ll mod(ll A) {
return A - (ll)(A / dp) * p;
}
虽然难以置信,但是真的快了!2800->1700
C 题和 D 题是捆绑的,C 是签到,D 是把第二层的和改成了异或和,也就是要统计每一位上有多少后缀和是1,就变成了金牌题。
小武一开始攻了好久没思路,去做毒瘤题J了,我和zxn把B改对之后,对这题也没啥期望了,但是我们讨论到一个有用的点,就是每当后面加入一个新数字,相当于把前面所有后缀和全部加一个数,再想求某一位是不是1,也就是求区间内有几个数,这个可以把区间加改成移动零点,只在询问时改变区间。这个思路貌似行得通,不过剩余时间不够了,也挺麻烦,脑子里全是晚饭了。
正解有两个比较妙的点,一是要求第 k 位为1的数字个数,就是 \(\mod 2^{k+1}\) 后值大于 \(2^k\) 的数个数。开 log 棵树状数组维护模上每个2整数次幂后值域分布。
二是把后缀和改为前缀和的差,\(a_i+...+a_n\) 改成 \(s_n-s_{i-1}\),这样前面的前缀和值都是不用变的,不用更新,询问时 \(s_n\) 的意义就是移动零点。这题就这么做完了,不过移动零点之后值域分了好几段,挺麻烦的说。
I 题是个镜子题,一开始dirt率很高有点不敢开,不过在队友的鼓励下还是去写了。
很重要的一点就是光线不会交叉,要么是环要么是链,维护起来比较方便。一开始我们以为求光线长度就行了,再读题发现询问是光线经过多少种不同的镜子,所以需要记录颜色进行预处理。
小武给出了一个非常简单的写法,就是每个墙向左向右看成两个不同的边,这样光线经过的边也不会交叉,且每条边的颜色也清晰明了。赞,很顺利地一遍过了。
J 题是左右摇摆撞墙数据结构,俩队友讨论了个分块再二分的做法,虽然我没参与此题不过我觉得很合理,复杂度是 \(n\sqrt n·logn\) ,一直觉得1s内能过很玄乎,不过没别的想法了就硬上,还挺难写,小武写了两个小时,全程刚这一题。可喜可贺的是最后竟然跑过去了。看了眼题解,分块改成了线段树,不过我们因为已经过了所以就没仔细再研究了。
第二场
名次:60
赛时:A B C E H I(6题)
赛后:G
E 签到找到结论了马上过掉,实际上判断一个数是不是2的整数幂只需要一个lowbit,我写复杂了。
小武写了C,我写了H,H是经典的机器人区间指令,只需要map记录每个位置有哪些时间在这里就行。
签到题写完去看B,一张图每次给出几个点,求子图最小生成树,这题卡了我们有一阵子。后来小武提出了根号分治的做法,小于根号个点直接n^2的prim算法,哇郎prim竟然真的有用处,省个log。而大于根号用克鲁斯卡尔怎么去log呢,发愁了一会儿突然发现这个log仅仅是预处理时的排序耗时,之后就是一条一条边加了,O(n)的。
实际上写的时候发现prim本身是n^2,但是判断边还是需要一个map的log,改成unordered快一点点但是还是T了。赛后jiangly在群里有提到可以把这个log处理掉,但是当时这题卡了好久,头昏脑涨实在是不想思考了,就学着去年济南站的B题,把根号向克鲁斯卡尔那边偏移了一点,最后均摊过了,赛后也没有过多管这道题。
之后我去攻 I 题,zxn和小武手玩 A,他们先做出了 A,我还在死磕 I。
A 题我没参与,但是这种砖头有个特殊的性质就是从边界出发的线条一定会在边界消失,中间不会断掉或者有个rho的形状。所以边界上的线条数是固定的,中间的圈就是多出来的数量。
zxn他们WA了两发之后改对了,我也刚好写完 I ,一起交,两边都过了。然后G死活不会做,就下班了。
I 题磕了一个多小时,终于发现了最大的规律,就是如果我决心要拿走某个区间,那么这个区间内比两边小的数可以直接改成两边的数,我只需要考虑中间更大的区间。
于是决心拿走的每个区间成了子问题,就是求每个这样的区间最大的价值。从最大的数开始求,区间内有更大的数就dp,而无需管更大数的区间是具体怎么取的,因为那是已经给求好的子问题。
G 题看了题解感觉好妙,原来这场两道根号分治。
虽然这题数字大小仅有1000,但是质数的个数还是不足以状压,直接来一手神之分治,将31以内的数看成小质数,数量可状压,37以上的数看成大质数,每个数只能有1个大质数。所以按照大质数进行分组背包,每个大质数的组需要选出偶数个数才能组成平方数。
\(f[0/1][i]\) 表示大质数个数奇偶,小质数奇偶状态为 i 的总权值,每加入一个数滚动更新到 g 数组,再让 f+=g。在分组背包中,我们把没有大质数的数看成第一组,即大质数是1,1的数量无所谓奇偶,不过其他大质数都要是偶数,所以每走完一个分组,那些 \(f[1][i]\) 的状态就不能再有贡献了,要舍弃掉,全部置为零。
第三场
名次:78
赛时:A B D E J L(6题)
赛后:H
L 是个数独有关的签,小武写了。B 是裴蜀定理。
A 是个很有意思但是赛后被好多人喷的题,模仿了去年沈阳的小船过河。
但是这题不是dp,而是一个结论题。每次至少L人能开船,最多运R人,我和zxn讨论了好久,把题意转为了:小船先无偿运R个人,然后每次要派L个人去来回一趟接R-L个人,每人花费两点体力。
这个该怎么分配体力呢,本来以为是个神仙问题,后来发现是脑筋急转弯。这就是好比有 (n-R)/(R-L) 次降水,每次选择 L 个位置降2点水,给你每个容器的高度问你能不能通过协调选择的位置来装下这么多次降水。直接求和肯定不对,但是把每个容器的上限改到降水次数的上限,再次求和就是对的了,一个小结论吧,也挺好证的,这题就做完了,称不上顺利,我俩卡了好久,但是终于摆脱了坐牢。
在过 A 之前就已经会 D 了,D 也没啥知识点,就是难写,断断续续地写了一会儿就过了。发现小武还在做 J,我过来问下情况,小武说需要再基环树上倍增,他把基环树分成了树和环分开讨论,特别难写。我有点懵。
我问为啥不能直接在基环树上倍增,他好像突然懂了,犹豫了一会儿就去改了。虽然浪费了一点脑容量和时间,不过还是顺利过了。
然后是 E 题,一个不听话的打字机,求最优策略能打出所有好单词的概率。
转到01trie上,手玩了一会儿,发现只需要考虑每个节点的分流问题就好,就是每当到了一个位置,我往 0 处走有 x 个好单词,往 1 处走有 y 个好单词,那么最优策略是哪里多就往哪里走。把每个节点分流成功的概率乘起来就是最后的答案。
zxn发现这个问题可以dp递推,就是把刚刚的情况分流成功概率设为 \(f[x][y]\),于是顺利解决了这题。期间小武去看 H 了。
H 是求最小的矩形,小武发现了个n^2的神仙做法,就开始写,我们觉得稳了,开始讨论晚饭吃啥。
谁知道写完后一交TLE了,又是一个纯n^2的做法T了,我们和第一场一样着急。
不过这次没有第一场那么幸运了,一直T到了比赛结束。赛后小武好像发现了个小规律优化了下,卡过去了,只能说有点遗憾吧。
题不是我开的,我也不想补了。
第四场
名次:73
赛时:A C F G H I J(7题)
赛后:B E
这场整体比较简单,同校的队 9 题了,杭电一队甚至 AK。
队友写了I,我写了G。A题题目名LCT,不过是个带权并查集。H 和队友猜了个gcd的结论过了,C发现是置换环,也很简单。签到题就已经五道了。
然后是F,每个输入只有一个数,很快啊!还没来得及闪,队友就找到规律猜了结论交了一发,但WA了。
队友认为这题想让点数最小就是一条链,我发现不止一条链,只需要让某些点全在另一头就行了,然后 u 和 v 中间留一半的点,这样总贡献就是点数平方,至于非平方数是否要加一,或加二,我们手玩了下发现不一定让中间和另一头的点数相同,也可以是 \(a(a+1)\) 酱紫,u 和 v 中间是一条链,在链上插一个点可以让贡献加一个数或者减一个数,这个数奇偶性和链长有关,所以 \(a(a+1)\) 的分配方式可以保证奇偶。
讨论了许久觉得天衣无缝了,但是还是WA,最后发现是1e18的数取sqrt炸精度了,哈哈,二分过了。赛后得知有个sqrtl。
J 题是小武在我俩讨论F的时候一个人开的,我全程没参与,不说了。
之后就去看 B 和 E 了,B 是有趣的拉箱子问题,由于碰到箱子后不能走箱子的那条路而是要找个次短路,我就去往圆方树那里想了,队友讨论了下发现可以把每条边两个方向当做点,边之间的连线虽然很多,但是可以用前缀和优化,还是O(n)的,拉箱子的步骤就相当于不能走回头路。我听了下觉得可行,就去看E了。
结果虽然 B 思路是对的,但是抛开细节不说,我们队最后十分钟终于找到错误改对了,却又因为卡常T掉了。虽然是个理论O(n)的算法,但是边数点数有点太多了,多了个前缀和优化后又多了一倍的边和点,牛客评测机有点吃不消。遗憾离场。
我这边虽然把 E 转化了题意,但是最后是个1e5的01背包,我却不会了,我想到高中时候见神鱼有写过多项式优化背包的技巧,不过我没学过,乱搞一通没有过。后来看题解小武说他会01背包的优化。。。如果让他来写估计就过了有点事后诸葛亮,因为他全场在调 B 也没时间顾我这边。
所以这次比赛的结果就是双卡。
B 题小武还是不舍得修改他的前缀和优化代码,我去看了眼题解做法,是按边BFS,队列里存的是边的编号,虽然听起来没啥区别,但是有些小细节。
除了小细节以外,如何优化菊花图边到边之间遍历的复杂度,题解有个非常妙的优化:每个点之会被vis两次,第三次没用。这是因为第一次vis到这个点,除了来边,其他边的dis值肯定都更新了,第二次vis这个点可以更新第一次的来边,之后第三次就不会更新任何边了,所以即使是菊花图的中心点,它周围的边最多只会遍历两边,保证了复杂度还是O(n)的。
E 题的背包优化问题我赛后补上了博客,只能说大佬熟知的典题和套路题我没见过是真的不会。二项式相乘的题,很多都可以用先ln再exp的技巧,而ln则是麦克劳林公式手动展开,无需费时间,只能说数学能力是ACMer一个很重要的木桶板。
E 题其实除了不会优化背包外,我还犯了另一个很致命的错误,就是我魔改的kmp复杂度假了。这题虽然只有一个字符串,但是我需要求出所有位置往后延伸一个错误字符后的失配位置,我选择是暴力跳nxt数组,这个其实字符全部相同就已经卡满了,但是我想当然以为复杂度是对的就没管。(后来得知包大爷死在了同一个位置哈哈,当时他还在群里问为什么他的exp板子这么慢)
正确的做法一个是建AC自动机,最保险,xxx队就是这么写的,另一个做法是魔改kmp到底,在nxt数组基础上再加一个 \(dp[i][0/1]\) 表示第 i 位下一个字符正确与否可以匹配多长的前缀。不过这个魔改做法泛用性还是没AC自动机做法好。
第五场
名次:36
赛时:B E H J L(5题)
赛后:
难度梯度最畸形的一场,四题队伍从 70 多名一直到 850 多名,全靠罚时。
前期我们队还是相当顺利,L 题找到结论10分钟就过了,小武写E去了,我和zxn讨论B,发现也是相当水的签到,半小时就过了三道题。
第四道题是个爆搜,在找结论无果后,小武觉得符合条件的链数量其实不多,况且我们有最优性剪枝,我就开始上手写,用到了之前CF1804E相同的邻边状压技巧,恰好跑进了1s。
算上一发dirt,前四题只有156的罚时,在4题队里也是相当靠前的。那一发超时提交是怎么会事呢?
一开始以为是爆搜复杂度不对,于是我开始试随机数据,结果30的时候能秒出,而34就完全跑不出来了,我们都觉得有点不正常了。结果最战犯的一集来了,这题的 n 取值是40,而我代码里状压的部分有一句 1<<n 直接炸了,改成 1ll 就过了,被队友揍了一顿后长记性了,以后再也不犯这种zz错误了。
然后开始终极坐牢。
J 题知道有个prufer序列,但是第3个限制谁顶得住啊。我发现数据范围甚至可以n^3,于是看看有没有突破口。
想到了一个统计的方式,第一层枚举 1 到 k 的链上有多少其他的点,然后再将其他点挂在任何点上面。
1 到 k 的链本身有 \(\frac {k!}2\) 种不同情况(算上翻转),将其他的点加在链上,就是个组合问题。至于其他挂着的点,我设计的是每一棵子树作为一个子问题,比如在 1 点挂 i 个点,把 1 强制设为子树的根,然后就是一个可以调用prufer定理的东西,这个也没有好写的式子,限制了prufer序列中每个数出现次数,其实就是个超组合数问题,我写了个分母带阶乘的类似指数型生成函数的式子预处理了所有子树大小的方案。(因为强制选了一个点作为根,它的度数要比其他点少1或2,取决于它是否是链首尾)
设有 i 个点插进了 1 到 k 的链上,之后就是把 (n-k-i) 个数挂在这个链上。分配每个点子树大小,也就是 (n-k-i) 个位置 (k+i) 个颜色的超组合,同样可以写成指数型生成函数的式子。
因为 exp 的首项不是 0,多项式快速幂选用了双 log 写法,\(n^2 log^2\),卡过去了。题解好像是没有 log 的背包写法,但和我的大体思路一样,我用多项式纯粹是大炮打蚊子硬套公式了。
虽然大概是两个半小时的时候就写完了 J 的代码,不过最后半小时才改对,讲一下这题调试时的曲折经历。
因为细节非常多,除了这个双层枚举的一般情况,我还特判了很多 d 和 k 比较小的情况,d 或 k 比较小都会影响到我式子的正确性。导致我代码能自测的最小样例是 5 3 3,这个样例一开始我代码跑了84,而 5 3 4 竟然也跑了个84出来,这很显然不对,要么是5 3 4算多了,要么是5 3 3算少了。而我和zxn手搓竟然好久搓不出到底是哪里错了,手搓了好几遍俩都算的84。。。无奈赶紧让小武写个暴力,虽然暴力也很难写,不过小武硬着头皮去写了。
两份代码buttle了一阵儿,暴力代码终于改对了,小武暴力跑5 3 3跑了108,可我还是不知道漏了什么。这时大概还有一个半小时结束,这题验证小样例竟然验证了我们一个多小时。我很生气地决定把84种情况全都写下来,画到纸上,我画5 3 3,zxn画5 3 4,我俩开始快乐植树,植了满满一页,然后开始对照。
对照了一会儿终于找到了问题,我这里有没画出来的情况,而我漏掉的逻辑和我代码里漏写的一个因子竟然完全吻合,不亏是我自己写出来的代码。就是在 i 个点插入链内的时候,我只考虑了链上的位置关系,却没乘上 \(C(n-k,i)\) 表示到底选哪 i 个数。
改了之后,一切都通畅了。五道题完全可以下班了,我们开始讨论晚饭吃啥。
黄大爷他们队过了 G,赛后也把 J 过了,我只能说很神仙,不过我们那天实在太累了,G 也是看都没看,就没赛后补题。