2026 YL 杂题
- P12506:
注意到 Sub 9 满足距离 \(\ge \frac{n}{2}\),这启发我们按照 \(\frac{n}{2}\) 个单位对平面直角坐标系分块。
这样分完块之后有一些很特殊的性质:对于每个块内,点可以直接连边且数量是 \(O(1)\) 级别的;同时,每个块只会向以他为中心 \(5 \times 5\) 的范围内连边。这使我们边的数量大大减小。
进一步的,我们观察到若块内有 \(\ge 3\) 个点就直接有解了。于是块内我们都不需要连边,仅需考虑跨块即可。
实现时我们不需要真正的去连边跑,而是用扩展域并查集可以轻松完成合并操作。时间复杂度 \(O(n)\) 带大约 \(50\) 的常数。
总结:扩展域并查集处理染色、观察子任务。
- P6790:
神秘科技:广义串并联图。
它是指对于任意 \(4\) 个节点,都不存在 \(6\) 条两两没有公共边的路径连接这个节点中的每一对节点。
这东西,它可以对一些特定的图进行收缩,即:
-
删一度点
-
删二度点
-
删重边
从而将整个图化简为只有一条边。
这类似于 DAG 上的拓扑排序,我们可以按照它进行 dp。
令 \(f_i\) 表示边 \(i\) 在生成树上的方案数,\(g_i\) 表示不在生成树上的方案数。
删一度点时,ans 直接乘 \(f_i\) 上去。
删二度点时,因为两条边不可能都不在生成树上且只有两条边都不联通时才不联通(对应 \(g_i\)),据此可写出转移柿子,删重边是类似的。
模拟赛 1
- CF2106D(前后缀)
思维断层:前后缀拼接思想。
- P4090(二分 + 贪心 check)
思维断层:没有发掘出单调性。
一头牛如果没有礼物,则其后的牛都没有,于是可以二分第一个没有礼物的牛。对于这个 mid,我们将其前面牛的 \(c_i\) 从小到大排序(为了尽量靠后插入),如果这样都有牛排在它前面就说明一定不行。
- P6216(转换 + 前缀和优化)
思维断层:没有转换研究对象,转为枚举回文串然后统计每个模式串的贡献。
显然可以跑一遍 manacher 得出每个点的回文半径,然后模式串的起点 \(j\) 如果在左半边,贡献是 \(l-j+1\),否则 \(r-j+1\)(\(l=i-len_i,r=i+len_i-m+1\),解不等式可得),推一下式子可以得到两个可以前缀和预处理的东西,就做完了。
- CF1989E(观察性质 + 前缀和优化 dp + 正难则反)
思维断层:观察出性质后没有看到方案数想 dp。
注意到 \(b\) 对于 \(a\) 中固定长度的连续相等段总是一样的。于是考虑分段进行 \(dp\),令 \(dp_{i,j}\) 表示前 \(i\) 个位置分 \(j\) 段的方案数。考虑到连续的长度为 \(2\) 的段和两个长度为 \(1\) 的段在 \(b\) 中表现形式一致,为了去重,得到转移方程:\(dp_{i,j}=dp_{i-1,j-1}+\sum_{k=0}^{j-1} dp_{i-3,k}\)。后面的 \(\sum\) 用前缀和优化,得到一个 \(O(n^2)\) 的做法,无法接受。考虑正难则反,算出总的方案数再减掉 \(<k\) 的即可,时间复杂度 \(O(nk)\)。
总结:观察性质的能力要加强;观察出性质要立刻思考做法;各种转化思想要运用起来。
模拟赛 2
- P9014(搜索)
思维断层:读错题了,下次读完题先模拟样例。
没啥好说的,看到表格、地图问题直接往搜索方面思考即可。(前提是数据范围在 \(\sim 10^3\) 级别)
- ABC261F(逆序对)
思维断层:想到逆序对了但没有继续深入,应当比较弱化问题与原问题的区别。
若没有 \(c\) 的限制则答案为逆序对个数,而对于原问题只需要对于每个 \(c\) 相同的段减去它们的贡献即可。
- CF1781C(构造)
思维断层:赛后思路基本想到了,是代码设计的问题。
构造题一般先考虑求答案,再考虑求方案。
- ABC164E(分层图最短路)
思维断层:根本没有想到分层图。应当看到限制思考分层图。
考虑建图,对于消耗银币,\((i,j) \to (nxt,j-a)\)(注意是双向,\(j\) 表示现有银币个数)
对于兑换银币,\((i,j) \to (i,j+c)\)。
模拟赛 3(线段树)
- AT_abc343_f
维护最大值、次大值、最大值个数、次大值个数。
- AT_practice2_l
维护 \(0\) 个数、\(1\) 个数、逆序对数量。
- AT_abc357_f
拆一下式子维护。太简单了不讲。
- AT_abc223_f(括号序列 trick)
对于括号序列的判定,我们令 ( 为 \(+1\),) 为 \(-1\),一般维护前缀和,只要满足区间和 \(=0\) 且任意前缀大于 \(0\) 即可。
任意前缀大于 \(0\) 可以转换为前缀最小值 \(>0\),就能线段树维护。
这里有交换操作,简单分析一下前缀变化,做两次区间修改即可。
- CF1234D
直接 \(26\) 个字母维护。
- AT_practice2_j(最大值的不减性)
线段树上二分。
- AT_abc322_f
P2572 原。
- P4145 / CF438D(看到特殊操作,暴力修改)
略。见括号内容。
代码细节汇总:
-
次大值可以在左(右)端点次大值和右(左)端点最大值中取得,不一定只是后者。
-
对于多信息维护,尽量用结构体。
-
结构体返回的非法状态不能与合法的重合。
-
结构体返回值尽量封装,也就是最好定义变量
NODE res,而不是返回{...}。 -
能分开维护的就分开,比如这题 \(0,1\) 的个数就最好分开维护。
-
修改完之后各个地方都要同步改,不能拆东墙补西墙。一定要通读一遍。
模拟赛 4(字典树)
- P3879
板。
cnt、exist计数类
- AT_abc287_e
遍历时经过其他的串走过的点就累加 \(\text{LCP}\) 长度,得到的就是 \(\max\)。
- P2922
是别人的前缀就在末尾加 \(cnt\),别人是自己的前缀就遍历时累加 \(exist\)。
- AT_abc353_e
注意到每次 \(cnt\) 的变化都意味着 \(\text{LCP}\) 的变化,于是记录 \(cnt\) 相等的连续段累加贡献即可。注意边界的处理。
最值问题类(二维偏序思想):
- P4551
板。
考虑树上异或路径,它总是由 \(x \to lca,lca \to y\) 组成,然而它又可以由 \(1 \to x,1 \to y\) 异或而成。因此将所有 \(1 \to x\) 的路径插入字典树,然后每次找不同的路径往下走(\(1 \to 0\),\(0 \to 1\))即可获得最大值。为什么呢?因为二进制下高位决定一切!
- CF923C
思维断层:根本没搞清楚插入什么、查询什么。
将 \(p_i\) 插入字典树,查询每个 \(a_i\),尽量走相同的路径从而拼出最优的 \(p_i\) 即可。
- AT_abc377_g
思维断层:没有想到可以维护加字母的代价。
这题当然可以用 trie,但显然 hash 更简单。
维护一个 map 存储每个前缀恢复成 \(S_{1 \sim k}\) 的最小代价。在计算贡献的同时计算前缀的 hash 值,若出现过则用这个最小代价加上删除的代价取 \(\min\) 就是答案。
- CF1285D
首先将 \(a_i\) 插入字典树。
因为最大值的构造总是按位贪心,所以对于有两个子节点的,最大值可以在这一位构造 \(1\),然后往最大值更小的分支去走。如果只有一个节点,那就没话说只能走这个儿子啦。
树形结构问题:
- AT_abc403_e(子树问题)
思维断层:没有把 trie 当作树来处理。
把 \(X\) 集合看作插入,\(Y\) 集合看作查询。
对于 \(X\) 集合,每插入一个字符串,它会影响的是其子树内的 \(Y\) 串。我们考虑打一个清空标记,随着递归下传到子节点上。这样我们就可以放心地在子树内递归累加要减去的贡献。
对于 \(Y\) 集合,查询时若路径上没有一个清空标记,就直接贡献加一即可。
- CF455B
思维断层:不会博弈论。
正解:
考虑建立已知字符串的 trie 树,我们发现两人每次操作都应该添加前一个人的子节点上的字母。
于是考虑设计状态 \(win_i,lose_i\),分别表示节点 \(i\) 是否能必胜或必败。
答案如何表示呢?分四种情况:
-
\(win_1=lose_1=1\),即先手可胜可败,显然先手胜利。
-
\(win_1=0,lose_1=1\),即先手只能保证失败,显然后手胜利。
-
\(win_1=1,lose_1=0\),即先手只能保证胜利,这样的话每次先手胜了之后下一轮后手就会胜,于是看 \(k\) 的奇偶性即可。
-
\(win_1=0,lose_1=0\),即先手既不能保证胜利,又不能保证失败,那么主动权全在后手,必然后手胜利。
如何转移?显然对于父节点 \(i\),若存在子节点 \(nxt\) 满足 \(win_{nxt}=0\),即可以走到让对手不能保证必胜,那么节点 \(i\) 一定必胜,\(win_i=1\);同时,若存在 \(nxt\) 满足 \(lose_{nxt}=0\),即能走到让对手不能必败,则节点 \(i\) 一定必败,\(lose_i=1\)。
总结:博弈论设计输赢状态,考虑 dp。
模拟赛 5
- CF1582C
思维断层:我也不知道我为啥当时不会
正解:枚举字母种类然后双指针计算答案即可。
总结:无。
- CF1689C
思维断层:没想到树形 dp。
正解:
简单 dp,令 \(dp_i\) 表示节点 \(i\) 为根的子树内能保留的最多节点数,显然叶子节点是 \(dp_i=0\);只有一个儿子就根节点被感染、儿子封锁,所以是 \(dp_i=siz_i-2\);有两个儿子就 \(dp_i=\max(dp_{nxt_1}+siz_{nxt_1}-1,dp_{nxt_2}+siz_{nxt_2}-1)\)。
总结:看到方案数、最值考虑 dp。
- CF1474C
思维断层:没有发现初步性质。
正解:不难发现第一次操作必须选最大的数(不然后面 \(x\) 更小,最大的数就没有匹配的了),所以我们枚举和最大值匹配的数,然后维护 muiltiset 模拟删除即可。
总结:看样例发现性质。
- CF1216E1
思维断层:我也不知道我为啥当时不会
正解:纯模拟,按 \(n\) 位数为一段累加。
总结:无。
- CF1974G
思维断层:没想到反悔贪心。
正解:贪心的每月都购买,如果钱不够了就挑大根堆中最大的加回来。
总结:反悔贪心一般流程:
-
考虑最贪心的做法
-
考虑返回条件
-
建立反悔机制
模拟赛 6
曹巨 /se
- B(从特殊入手、插板法技巧、后缀和优化)
考虑 \(k=1\),相当于 \(n\) 个球放入 \(m+1\) 个盒子(\(0 \sim m\))中,允许空盒(意味着没有一个数是该取值)。直接插板法,答案是 \((m+1)^n-{n+m \choose m}\)。
对于 \(k>1\),因为这个限制导致 \(a_i\) 衰减地很快,非零的值仅有 \(\log\) 级别。考虑对值域进行 dp,令 \(dp_i\) 表示值为 \(i\) 的方案数。从大到小转移,每次按照限制寻找决策点,答案即为 \(dp_0\),复杂度是 \(O(n \log n)\),需要一个后缀和优化。
- C(约束条件的转化+数据结构维护决策点+逐项抵消加速转移)
考虑字符串匹配过程,容易发现对于 \(i<j\), \(T_{i} \le T_{j}\) 等价于 \(S[i+1,j] \le S[i,j-1]\),这个画一下图可以知道。
也即,对于 \([i,j-1]\) 这个区间中第一个满足 \(S_k \neq S_{k+1}\) 的 \(k\) 需要满足 \(S_k \le S_{k+1}\),对于 \(i>j\) 是类似的。
以下,我们不妨令 \([i,j-1]\) 为第一类约束区间,\([j,i-1]\) 为第二类约束区间。
这样我们找到了 dp 的思路,设计 \(dp(i,j)\) 表示前 \(i\) 个字符,且第 \(i\) 个字符为 \(j\) 的方案数。接下来分情况转移:
-
\(S_i=S_{i+1}\),直接转移,\(dp(i,j) \to dp(i+1,j)\)
-
\(S_i<S_{i+1}\),枚举上一个 \(S_k \neq S_{k+1}\),我们发现此时不能存在第二类约束区间 使得 \(k<l \le i,r \ge i\),容易发现满足条件的决策点 \(i\) 形成一段后缀,可以使用 set 维护决策点(每次删除 \(i-1\) 对应的左端点,添加右端点那么多个 \(i\) 即可),\(dp(k,j) - dp(k-1,j) \to dp(i,x)\),其中 \(j>x\)。
-
\(S_i>S_{i+1}\),即不能出现第一类约束区间,其余同理。
实现上我们不需要真的枚举 \(k\),考虑 \(dp(i-1,j)-dp(i-2,j)+dp(i-2,j)-dp(i-3,j)+...+dp(k,j)-dp(k-1,j)=dp(i-1,j)-dp(k-1,j)\)(逐项抵消),这样我们可以 \(O(1)\) 的完成转移了。
- D(观察数据范围和性质+懒惰标记思想)
我们发现值域很小,往这方面进行思考。
对于一个值 \(x\),我们观察出两个性质:
-
选成负数的总是等于 \(x\) 的值的一段后缀(显然)。
-
对于最后一个没被选成负数的 \(x-1\),它的位置一定在所有 \(x\) 前面(否则可以把 \(x\) 换成选 \(x-1\) 会更优)。
这样,我们可以得出一个贪心策略:按值域从小到大考虑,二分后缀的长度,贪心地选取后缀。这个看上去就很对。
但是你写起来就会发现颇有难度,事实上你会发现你根本没法维护每个值的真实状态。我的实现中采用了一种类似于 lazytag 的做法,就是先假设 \(<x\) 的值都为 \(-1\),其余为 \(1\)。先对于这个数组维护前缀和、前缀最小值之类的东西,同时还得维护一个偏移量保存当前状态与真实状态的差值,然后二分 check 时再将这个偏移量减回来即可。具体见代码实现。
模拟赛 7
链接同模拟赛 6。
- A(发掘特殊性质)
这个题就是暴力。。。
首先我们(并不)容易发现 \(cnt_i\) 最多只有 \(O(\sqrt{n})\) 种取值。
证明:
考虑最坏的情况:\(cnt_i\) 为 \(1,2,3,...\)。
由于 \(\sum cnt_i =n\),然后这是个等差数列,根据等差数列求和公式可知 \(\frac{m(m-1)}{2}=n\),显然最大值 \(m\) 就是 \(\sqrt{n}\) 级别。
有了这个结论,我们可以直接枚举 \(cnt_i\) 和对应的 \(i\)(可以搞个 vector 存一下),然后每个 \(cnt_i\) 对应的 \(i\) 可以从大到小排序优化一下,最后加一个最优性剪枝即可通过。信仰的力量
- B(二分 + 2-SAT + 线段树优化建图)
拼好题。
首先这个最大最小距离显然可以二分答案,考虑如何 check。
然后由于 \(x_i\) 选了 \(y_i\) 就不能选,以及与 \(x_i\) 距离小于 \(mid\) 也不能选,所以这个构成了一组 2-SAT 问题,我们可以建图跑 tarjan 来 check。
但是你需要对一个区间建图,直接做是平方级别,所以我们线段树优化建图即可。
这里放一下建图部分的代码,算是积累一个代码技巧:
void build(int p,int lt,int rt){
tree[p].lt=lt;
tree[p].rt=rt;
tree[p].id=++all;
if(lt==rt){
G[tree[p].id].push_back(op(node[lt].second));
return;
}
int mid=(lt+rt)>>1;
build(p<<1,lt,mid);
build(p<<1|1,mid+1,rt);
G[tree[p].id].push_back(tree[p<<1].id);
G[tree[p].id].push_back(tree[p<<1|1].id);
}
void upd(int p,int ql,int qr,int u){
if(tree[p].lt>qr||tree[p].rt<ql)
return;
if(ql<=tree[p].lt&&tree[p].rt<=qr){
G[u].push_back(tree[p].id);
return;
}
int mid=(tree[p].lt+tree[p].rt)>>1;
upd(p<<1,ql,qr,u);
upd(p<<1|1,ql,qr,u);
}
- D
浙公网安备 33010602011771号