MX 练石 2025 NOIP #16

唉……以后再也不偷懒不手动开栈了,莫名其妙 RE 浪费了这么长时间……

以前是因为,有时候手动开栈会不能编译,说是开的太大了,不过我就开了 256MB 啊,机房电脑还是太杂鱼了。

不过开不起的时候重启一下就能开的起了(


2025 --【炼石计划 NOIP】-- 第十六套

链接:link
题解:link

时间:4.5h
题目数:4
难度:

A B C D
\(\color{#F39C11} 橙\) \(\color{#3498DB} 蓝\) \(\color{#52C41A} 绿\) \(\color{#BFBFBF} ?\)
*1100 *2200 *1900 *?

估分:100 + [40,60] + 45 + 40 = [225,245]
得分:100 + 40 + 45 + 40 = 225
Rank:21/132


场祭

读题。

A 签。

B,先是推出来了个只有两个数的结论,答案可以 \(O(1)\) 计算。然后尝试推广,但是发现似乎很不好推广,不过观察样例发现答案不会太大,所以是不是可以前几个数暴力,后面直接 bitset 优化 dp?似乎是可以的,前几个数两两判一下答案取 min 就可以了。

写一半发现这还是没有推广啊!又思考的一会儿,直到 2h 的时候还是毫无头猪,果断去打暴力。注意到答案是递减的,而且后面答案会变得非常小,所以可以从大到小开一堆 bitset,每次选大小 \(\ge\) 当前答案的最小的 bitset 来做 dp,这样应该会优化不少常数,卡一卡说不定能把随机数据给冲过去。

然后就是开头的问题了,一直神秘 RE,关键是找不出错来,浪费的时间足以把 D 的暴力打完还剩不少了……

最后只给 CD 留出来 1h 多一点了,快速打了 C 的暴力,u1s1 还是挺良心的,无脑暴力给了 45pts(

D 注意到链的部分分足足有 40pts,而且做法还很简单,大力分讨就可以了,规约一下也就十几种情况,而且都很好想,迅速写掉了。

不过因为前面的原因,没时间去打 那个 12pts 的暴力了 /ll,输,很害怕 NOIP 会因为这种原因痛失 1=……

题外话:B 因为不同大小的 bitset 不能丢到同一个数组里,我也不怎么会使用 define 科技,所以被迫写了(拿代码生成了)27KB 的代码去枚举要更新哪个 dp 数组(


补题

学习了顶级 define 科技,C++ 太强大了。与 gpt 的对话

补 B,是同余最短路,属于是太长时间不用忘了可以用这个东西来做了。

考虑当只有一组询问(\(a_1 \sim a_n\))时该怎么做,\(\forall j \in [2,n] , i \in [0,a_1)\) 建图 \(i \xrightarrow{a_j} (i + a_j) \bmod a_1\),然后跑一遍最短路,答案就是 \(\min \{ dis_i \} - a_1\),这是因为 \(dis_i\) 的意思是能到达的最小的 \(\bmod a_1 = i\) 的位置,于是再减去 \(a_1\) 就是最大的不能到达的了。

那么多组询问,直接跑 dij 是 \(O(n^2 V \log (nV))\) 的,显然不行,不过考虑 \(i \to i+1\) 时,在上一次 dij 后得到的 dis 数组的基础上(把 dis 数组直接表示为 \(a_1\) 条边),再加 \(a_1\) 条以 \(a_{i+1}\) 为边权的边跑一遍 dij,复杂度就变成了 \(O(nV \log V)\)

据说这时候把 dij 换成常数较小的 SPFA 就可以冲过去了,但是我不会 SPFA 的优化qwq

注意到还有一个性质没有利用,就是每次加入的边的边权是一模一样的。于是可以考虑类似归并(双指针)地更新以 \(a_{i+1}\) 为边权的这 \(a_1\) 条边,具体地,实时维护一个递增的 dis 数组,然后模拟优先队列的过程每次选 dis’ 最小的点。不难发现,dis’ 最小的点一定是 dis 数组中未访问过的最靠前的点 或 已经用新加入的边更新过的 dis’未访问过的最靠前的点,于是维护个双指针就好了。

具体地,维护 dis 表示更新前的最短路,tmp 表示更新过(必须用到新的边)的最短路,di2 表示最终的最短路,vis 标记每个点是否访问过。且需要保证前三个数组都是递增的。最终目的是得到 di2,于是先需要枚举一个 ind 表示该填 di2 的哪一位了,然后循环内按照上面的策略找到最小点 \(u\),更新 di2,同时考虑 \(u \xrightarrow{a_{i+1}} (u + a_{i+1}) \bmod a_1\) 这条边,把 \((u + a_{i+1}) \bmod a_1\) 放到 tmp 的末尾即可。

rep(i,0,a[1]-1) vis[i]=0;
int i=0,j=0,r=0;
rep(ind,0,a[1]-1)
{
    while(i<a[1]&&vis[dis[i].nd]) ++i;
    while(j<=r&&vis[tmp[j].nd]) ++j;
    
    if(dis[i].st<tmp[j].st||j>r) di2[ind]=dis[i++];
    else di2[ind]=tmp[j++];
    vis[di2[ind].nd]=1;
    tmp[++r]={di2[ind].st+x,mod_(di2[ind].nd+x)}; // x 即 a[i+1]
}
rep(i,0,a[1]-1) dis[i]=di2[i];

补 C,是简单题。

先做特殊性质。首先注意到结论:对于一个串 \(s\),令 \(c_i\) 为字母 \(i\) 的出现次数,如果 \(k > c_{\tt A}\),那么所有 \(\tt A\) 一定都会被选,调整法证明即可。然后在剩下的 \(\tt B\) 中,一定是选一段后缀。于是最终的子序列就确定下来了。

考虑推广。显然还是先选上所有 \(\tt A\),然后发现一个结论,就是在用 \(\tt A\) 把原串划分乘若干个区间后,一定会选满若干个最靠后的区间,还是调整法证明。然后考虑没选满的那个区间,发现其实就是一个同构的子问题,在里面选上所有 \(\tt B\) 再划分区间,继续递归下去即可。

例子:考虑串 \(\tt \ldots A \ldots A xxxxx A yyyyy A zzzz\)(小写字母代表任意字母)如果 \(\tt z\) 没有选完就选了 \(\tt y\),一定没有把一个 \(\tt y\) 替换成后面的 \(\tt z\) 更优。然后假设把 \(\tt x\) 选完的总长度已经 \(>k\) 了,那么就是在那一段 \(\tt x\) 中选一个长度为 \(k'\) 的字典序最小的子序列了,显然是同一个问题。

至于比较字典序,可以先比较前面相同的连续字符,那么后面的字符就是 \(s_{l \sim r}\) 的一段后缀了,可以哈希 + 二分实现。


天依宝宝可爱!

posted @ 2025-11-18 14:41  little__bug  阅读(8)  评论(0)    收藏  举报