2025 暑假代码源 B 队集训总结

题单会分段,前一段是自己会的,后一段是赛后补的。

Day1 - 2025/7/21 - 贪心构造交互思维:

https://vjudge.net/contest/732129

A:从高位往低位考虑,每次尽量不经过位为 1 的点,将其删掉,\(O(n)\) check 是否可以从 1 到 n 即可。

B:注意到一次操作将一段区间变成相同,继续将这段区间操作可以将其变为 0,所以当 \(n\ge 4\) 时总和可以到达 \(\max\times n\),又有当 \(a,b>0\)\(|a-b|\le\max(a,b)\),所以这就是最大值。\(n\le 3\) 的情况是简单的。

F:注意到蛇头蛇尾若不在同一行,那么对蛇头蛇尾所在行和列进行查询肯定是奇数,再花四次操作查询是哪两个点。若蛇头蛇尾在同一行(列),我们可以用 \(2n\) 此操作得到蛇头蛇尾分别所在列(行),对其中一列(行)的前缀二分,这是因为查询的奇偶性是单调的。

G:最大化最小值考虑二分答案,首先可以求出每个点至少要被区间覆盖多少次,将区间按照 \(l\) 从大到小排序,第一个数只能被 \(l=1\) 的区间覆盖,第 \(i\) 个数只能被 \(l\le i\) 的区间覆盖,从左到右考虑每个数,如果这个数还需要被覆盖,那么肯定选 \(r\) 尽可能大的区间,这样模拟即可。


C:首先观察到序列和必须是偶数,且不能有任何一个数大于 \(\frac S2\)。条件太奇怪了,先考虑 \(3\) 个数的情况,一步操作是好判断的,若不行的话,我们可以找到 \(a+b>c\)(反过来是对称的),由于 \(\max\le\frac S2\),那么将 \(a,b\) 的总和减去 \(c\),两次操作就够了。考虑将这个东西扩展为 \(n=n\) 的情况,我们总可以找到一个 \(p\) 使得 \(\sum_{i=1}^{p-1}a_i,a_p,\sum_{i=p+1}^na_i\) 均小于 \(\frac S2\),仿照 \(n=3\) 的情况操作即可。

D:\(n\) 个区间的交并关系太复杂了,先考虑两个区间的情况。两个区间有相离,相交和包含的关系。相离将两个区间都选 N 即可,包含将内部区间选 N 外部区间选 T 即可,相交一个选 N 一个选 T 即可。\(n\) 个区间,我们可以先将相交和包含的情况取出来,最后肯定会剩下两两相交的区间,将其按照 \(l\) 排序,奇选 T 偶选 N 就可以了,可以证明是对的。

\(\color{red}\text E\):神仙题,先不考虑 2 的情况,将所有偶数位的字符反转,那么条件就变成了删除相邻且不同的字符,手玩一下就会发现答案是 \(|cnt_0-cnt_1|\)。有 2 的话就很简单了,每一个 2 可以为 \(cnt_0\)\(cnt_1\) 加一,处理一下就好了。

H:先将每个字符串内部匹配了,最后肯定剩下 ((((())))))))(((,我们肯定想要将左括号尽可能的放在前面,所以如果一个字符串的左括号比右括号多肯定要放在前面,如果都是左比右多,那么按照右从小到大排序,如果都是左比右少,那么按照左括号从大到小排序。


Trick:

  • 如果 \(n\) 比较大的情况难以分析,可以从 \(n\) 较小的入手。
  • \(\color{red}\text{E}\) 题的 trick,将相同转化为不同。

Day2 - 2025/7/22 - 模拟赛+数据结构(1):

模拟赛总结:https://www.cnblogs.com/lrx-blogs/p/18999574


https://vjudge.net/contest/732415

A:第一次肯定要选择最大值,我们枚举第一次选择的另一个元素,然后模拟即可。

B:用线段树维护右边第一个严格小于 \(\frac x2\) 的位置,然后 \(i\) 的答案,就相当于要求后缀和。断环成链要开 3 倍。

E:考虑暴力,先将数从小到大排序,若当前的答案为 \(k\),那么若 \(a_i\le k\) 可以直接将 \(k\gets k+a_i\),否则就直接退出循环。我们将这个暴力用数据结构优化,我们用可持久化线段树维护一段区间的值域线段树,那么每次就是找求 \([1,ans]\) 的数的和,然后 \(ans\gets result+1\),并且这么做特别的快,感性理解每次 \(ans\) 至少乘 \(2\)(前几次不一定)。

F:差分 \(\operatorname{get}(l,r,x)=\operatorname{get}(1,r,x)-\operatorname{get}(1,l-1,x)\),然后就可以用莫队暴力维护。


C:用 set 存所有的关键点,发现新加一个关键点相当于区间覆盖等差数列,达标记维护即可。

D:由于 \(b\) 是排列,由调和级数序列总和为 \(O(n\log n)\),在线段树中的每个节点维护还要加多少次商可以加一,维护区间次数最小值,余数的和,区间次数没有 \(0\) 就直接达标记,否则直接暴力递归到叶节点修改,由势能分析复杂度为 \(O(n\log^2 n)\)


Trick:

  • 可以先考虑暴力,再进行优化。

Day3 - 2025/7/23 - 数据结构(2):

https://vjudge.net/contest/732718


A:首先发现可以求出最后一个 \(a\),倒数第二个 \(a\) 可以用二分求出,我们用线段树维护还剩多少个数,线段树二分即可。

B:由于每次只是减少,那么每次修改答案最多加一,用 set 暴力维护,时间复杂度 \(O(n\log n)\)

D:若 \(x\) 在集合内,且最小的比 \(x\) 大的数为 \(y\),那么我们在线段树里存 \(a_x=y-x-1\)(不在集合里的数肯定不优),线段树维护区间 max,线段树二分找到第一个大于或等于 \(k\) 的位置即可。修改可以用 set 实现,清空可以使用动态开点线段树。

E:首先观察到 \(\min(B_j)\) 是没有用的可以直接变成 \(B_j\),类似区间最大子段和用线段树维护即可。

G:设 \(nex_i\) 表示 \(i\) 后面的第一个与 \(a_i\) 相加为 \(w\) 的数,放在线段树里维护就相当于区间最小值。但是这样修改会有 \(O(n)\) 次,考虑将 \(nex\) 相同的 \(i\) 只保留最后一个这样修改次数就是 \(O(1)\) 的了。


C:考虑一个子序列,如果它符合条件,当且仅当序列中的每个相邻的数的中间都大于等于这两个相邻的数,那么可以 dp,设 \(f_i\) 表示以 \(i\) 结尾的子序列的方案数,那么转移就是枚举 \(j\),使得 \(\min[j:i-1]\ge\min(a_j,a_i)\),单调栈优化转移就可以了。

F:颜色段均摊的结论,每次覆盖最多加一个颜色段,全局颜色加可以记录一个全局 tag,颜色覆盖可以用线段树找到每一段颜色全部相同的然后打 tag 维护,时间复杂度均摊分析一下是 \(O(q\log n)\) 的。

H:一段时间加边,考虑线段树分治维护,那么题目便转化为并查集连通块加,考虑像线段树一样达标记,\(tag_u\) 表示并查集给 \(u\) 的子树加 \(tag_u\),将 \(x,y\) 合并的时候,由于 \(y\) 不能算上 \(x\)\(tag_x\) 贡献,所以要先将 \(tag_y\gets tag_y-tag_x\),最后撤销的时候再加回去即可。

I:首先将衣服按照价格排序。转换枚举的维度,暴力是枚举人,我们可以枚举衣服,那么题目就变成了将大于 \(c\) 的人的钱减 \(c\) 并将答案加 \(1\)。考虑平衡树,将平衡树分裂成值域分别为 \([0,c),[c,+\infty)\) 的两段,在 \([c,+\infty)\) 的那一段打上 \(-c,+1\) 标记,然后再合并。可惜这样是不正确的,因为平衡树合并要值域不交,这样值域会相交。自然地,我们将平衡树分裂成三段:\([0,c),[c,2c),[2c,\infty)\),第三段可以直接打标记维护,然而第二段貌似不好处理。但是我们注意到第二段的元素 \(-c\) 后,值域砍半,这说明如果我们暴力将第二段的元素 \(-c,+1\) 后插入平衡树复杂度貌似是对的,实际上每个数最多被暴力处理 \(\log v\) 次,所以这样做是对的,时间复杂度:\(O(n\log n\log v)\)


Trick:

  • 类似 D、E、G 的思路,数据结构里维护的某些东西可能是没用的,将这些优化掉。

  • 并查集连通块加,可以通过打子树加标记实现。

  • Day2 D、Day3 I 运用了势能分析的手段,Day3 B、F 运用了均摊分析的手段,你以为的暴力其实复杂度正确。

    Day3 B、F 都是每次操作只会 +1,每次暴力修改可以做到 \(O(1)/O(\log)\) -1,类似 SA 求 height 数组每次最多 +2 均摊复杂度也是对的。

    Day2 D 题运用了势能分析的手段,定义 \(i\) 的势能为 \(\lfloor\frac{n}{b_i}\rfloor-\lfloor\frac{a_i}{b_i}\rfloor\),你会发现每次暴力修改势能会减少 \(1\),但是势能总和是 \(O(\sum_{i=1}^n\frac{n}{b_i})=O(n\log n)\) 的,所以总复杂度是 \(O(n\log^2 n)\) 的。

Day5 - 2025-07-25 - 模拟赛+数据结构(3):

模拟赛总结:https://www.cnblogs.com/lrx-blogs/p/19005298


https://vjudge.net/contest/733385

A:首先可以线性求出 \(f(1,i,a_i)\)\(f(i,n,a_i)\),然后用树状数组跑一遍“逆序对”就行了。

D:过去多少秒的时间内不小于 \(y\) 很难维护,暴力是直接枚举时间,转换成枚举每个元素(“转置”一下),那么题目就转换成了后缀加,前缀查有多少个数不小于 \(y\)。这个东西用线段树很难维护,所以考虑分块,在每个块内维护已经排好序的另一个数组,修改就打 tag(因为全局加不改变大小关系),散块暴力加。查询在每个块内二分,散块暴力即可。时间复杂度:\(O(n\sqrt n\log n)\)

C:观察到每个数如果小于 \(x_i\),那么之后肯定也小于 \(x_j(j\ge i)\),用两个线段树分别存大于 \(x\) 和小于 \(x\)\(a\) 的和,小于 \(x\) 直接乘加线段树,大于 \(x\) 维护区间 min 用势能线段树就可以了。


F:令 \(a_x=y\),将题目转化一下,即要求 \(\max-\min=r-l\) 的区间 \([l,r]\) 的个数,考虑对 \(r\) 扫描,然后用线段树维护每一个 \(l\) 对应的 \(\max-\min+l\),求刚好等于 \(r\) 不好算,但是注意到 \(\max-\min+l\ge r\),那么只需要在线段树里维护最小值即最小值个数就可以算了。考虑每次增加 \(r\)\(\max,\min,l\) 会怎么变,\(\max\):建出后缀最大值单调栈,加入 \(i\) 的时候会弹出一些栈的后缀最大值,将这些后缀最大值一一区间修改即可(类似【NOIP2022】比赛的第一步转化),\(\min\) 亦是如此,\(l\) 显然不会动。

B:扫描 \(r\),在线段树里维护每一个 \(l\) 是否满足要求,用单调栈维护出前一个大于 / 小于 \(a_r\) 的位置 \(p,q\),如果 \(a_{r-1}>a_r\) 那么最大值肯定不会改变,最小值会改变,具体是 \([q+1,r-1]\) 的最小值变成 \(a_r\),那么线段树的 \([q+1,r-1]\) 设为 \(0\),最大值的改变也同理。用单调栈求出 \(p,q\),线段树区间赋值,区间求和即可。

Trick:

  • 转换枚举维度。(这个太重要了,这次集训起码出了 \(5\) 道这种题)
  • 一些直接做不好做的题目(维度太高)的题目,可以考虑使用扫描线降低一个维度,每次扫描的时候还可以用到前面的结果进行递推,Day5 B。

Day6 - 2025-07-26 - 字符串:

https://vjudge.net/contest/733704

B:先直接 kmp 匹配,遇到匹配成功的就将这一段删去,然后重新跑 kmp,用栈维护字符串的删除。

C:考虑暴力,先求出 \(s\) 的 fail 指针,然后 \(t\) 直接暴力跳 fail 指针,当只有一个字符串 \(t\) 的时候,这个操作是均摊线性的,这里有多个 \(t\),每次暴力跳 fail 可能会跳很多,变成平方的了。暴力跳 fail 显然不优,我们可以预处理出 \(to_{i,j}\) 表示从 \(i\) 开始往根跳,找到第一个 \(s_{anc+1}=j\) 的点,\(anc\) 即为 \(to_{i,j}\),每次跳 fail 就是 \(i=to_{i,s_i}\),这样跑复杂度就不是均摊正确了,而是直接正确。其实这个东西叫做 kmp 自动机。

D:直接以 \(10\) 为底,随机模数单 hash 就可以了,唯一需要注意的一点就是枚举的时候,由于加法只会多一位,可以枚举 \(a\) 的长度,然后 \(O(1)\) 判断。

E:先考虑左边,肯定先用长的回文串吧,因为短的回文串可以在长的回文串中的另一边找到。然后我们发现删右边和左边不交的地方和其他方法一样优(左边不删完的情况),所以直接枚举就可以了,用个 manacher 求回文串即可。

F:魔改版的 kmp,首先将 \(p\) 置换,题目转化为 \(h\) 中有多少段离散化后与 \(p\) 相同。同样考虑 kmp,但是匹配的形式变了,两个字符相等改为了两个数的排名相同,直接做就没了(?)。你查排名需要用到可持久化线段树,会炸空间。但是我们发现 kmp 在跳 fail 的时候匹配长度都会减少,并且每次只会增加 \(1\),那么我们用树状数组维护,每次跳 fail 的时候暴力删均摊复杂度是对的,时间复杂度:\(O(n\log n)\)

H:这么奇怪的贡献式子一看就是后缀数据结构。建出后缀数组,找到 height 数组的前面第一个小于 \(h_i\) 和后面第一个小于 \(h_i\) 的位置,枚举 height(即子串长度),然后前缀和找出这段区间有多少个 \(0\) 就可以了。

G:显然枚举分界点,然后转化成求 \(f_i\) 为有多少个 \(s_j\) 使得 \(t[i-|s_j|+1,i]=s_j\)。直接 AC 自动机,单点加,点到根的路径查,转化一下贡献变成子树加,单点查,用树状数组维护即可。

当然你如果不会 AC 自动机的话,这题有根号分治的做法。当 \(|s_i|\le B\) 的时候直接枚举长度,用 hash 加 hash 表维护数量,当 \(|s_i|\ge B\) 的时候字符串的数量不多,直接暴力 kmp 匹配就可以了,时间复杂度:\(O(nB+\frac{n^2}B)\)。当 \(B=O(\sqrt n)\) 的时候时间复杂度为 \(O(n\sqrt n)\)


A:观察到拆分操作就相当与循环位移,然后直接暴力 dp 是 \(O(nk)\) 的(或许能过),但是发现大部分的 dp 值是相同的,将其合并,可以优化成 \(O(k)\) 的复杂度。

Trick:

  • 预处理下一个跳到的位置,kmp 不需要暴力均摊跳,而是直接跳。(kmp 自动机)
  • kmp 的匹配条件不只是字符串相等,也可以是其他的东西。
  • kmp 暴力跳 fail 的时候中途暴力修改复杂度均摊正确。
  • kmp 的题目可以建出 fail 树来考虑。

Day7 - 2025-07-27 - dp(1)+模拟赛:

模拟赛总结:https://www.cnblogs.com/lrx-blogs/p/19007323


https://vjudge.net/contest/733981

A:设 \(f_i\) 表示前 \(i\) 个元素全部染成红色的方案数,往右传递颜色是简单的,往左传递颜色可以考虑求出左边第一个 \(0\) 的位置,将这一段全部向左传递只需要花费 \(1\) 的代价。

C:直接单调队列就可以了。(其实我是交的之前的代码,忘记了)


B:设选择了 \(i\) 个,设剩余空间为 \(v\)\(w\) 为这 \(i\) 个的 \(b_i\) 之和,设 \(m\) 为所有的 \(b_i\) 之和,那么贡献式为 \(w+\min(\frac{m-w}2,v)\),显然要让 \(w\) 越大越好,直接做一个 01 背包就可以了。

Day8 - 2025-07-29 - dp(2):

https://vjudge.net/contest/734500

A:复制的之前代码。(

B:先预处理出第 \(i\) 个矩形拿到 \(j\) 分最少需要 \(val_{i,j}\) 次操作,然后背包 dp 就行了。

C:设 \(f_i\) 表示以 \(i\) 结尾的最长长度,用线段树维护第 \(i\)\(1\) 最大的 \(f_j\)\(s_{j,i}=1\)),然后转移就可以了,区间 chkmax,区间求 max,用线段树维护即可。

D:这个序列还是很有性质的,所以考虑倍增,设 \(f_{0/1,i,j,k}\) 表示从 \(j\) 开始走 \(2^i\) 步(\(0\) 表示按照规则走,\(1\) 表示按照规则取反走)是否能到 \(k\),求最长路就倍增看可达性就行了,时间复杂度:\(O(n^3\log V)\),不可过。但是可以用 bitset 优化变成 \(O(\frac{n^3\log V}{w})\)

J:首先算出每只猫要被接所需要的最晚出发时刻,将 \(t\) 排序,然后 \(p\) 个饲养员一个一个走,设 \(f_{i,j}\) 表示考虑到第 \(i\) 个饲养员,第 \(j\) 个猫的最少等待时间,然后发现可以斜率优化。


E:看到数据范围想到区间 dp,但是这样出发点就不是 \((0,0)\) 了,由于只要考虑 \([l,r]\) 里的点,所以可以将出发点设为 \((\min_{i=l}^rx_i,\min_{i=l}^ry_i)\)。转移即为 \(f_{i,j}\gets f_{i,k}+f_{k+1,j}+|\min_{i=l}^kx_i-\min_{i=k+1}^rx_i|+|\min_{i=l}^ky_i-\min_{i=k+1}^ry_i|\),注意要设一个 \(0\) 节点表示 \((0,0)\)

\(\color{red}\text H\):神仙贪心题(其实也可以 dp,dp 可能就不神仙了)。首先将每个人分成 \(a_i\le s_i\)\(a_i\gt s_i\) 两个集合 \(S,T\)\(S\) 中肯定按照 \(s\) 从小到大排序(按照 \(a\) 也可以),\(T\) 中肯定按照 \(a\) 从小到大排序,接下来考虑 \(S,T\) 之间的元素的关系,若 \(i\in S,j\in T,s_j<a_i\le s_i<a_j\),那么 \(i,j\) 只能选一个,且选 \(i\) 更优,其他情况可以任意顺序一起选。所以将元素按照 \(\max(s_i,a_i)\) 为第一关键字排序,以 \(s_i\) 为第二关键字排序(因为可能会出现 \(s_j<a_i\le s_i=a_j\) 的情况,先选 \(j\) 更优),然后直接按照排序后的顺序选就行了。

参考了 Alex_Wei 的题解

F:将除 V/K 的字符设成 A,一个结论是将一个序列交换两个数进行排序的最少操作次数=冒泡排序的操作次数=逆序对个数,将一个序列通过交换两个数变成另一个序列的最少次数是其置换的逆序对个数。这道题字符会重复,但只需将映射改成映射到没被匹配的第一个相同元素即可。于是设 \(f_{i,j,k,c}\) 表示选了 \(i,j,k\)V/K/A 最后一个字符是 \(c\) 的逆序对个数,记录 \(c\) 是因为不能出现 VK 的子串,直接转移即可。

I:设 \(f_{j,i}\) 表示前 \(i\) 个数分成 \(j\) 段的最小花费,转移为 \(f_{j,i}=\min_{0\le k<i}\{f_{j-1,k}+w(k+1,i)\}\),猜测 \(w(i,j)\) 是满足四边形不等式的,所以 \(f_{j,i}\) 满足决策单调性,可以直接用分治做 \(k\) 层。现在的问题是如何快速求 \(w(k+1,i)\),其实我们并不需要 \(O(1)\) 求出这个东西,如果我们能够在 \(O(r-l+1)\) 的时间内求出 \(w(l,r)\),那么复杂度均摊也正确,所以我们可以用莫队的方法求这个东西,复杂度正确。

Trick:

  • 将一个序列交换两个数进行排序的最少操作次数=冒泡排序的操作次数=逆序对个数,将一个序列通过交换两个数变成另一个序列的最少次数是其置换的逆序对个数。(有重复元素把映射改一下即可)
  • 遇到 \(f_{j,i}=\min_{0\le k<i}\{f_{j-1,k}+w(k+1,i)\}\)\(f_i=\min_{0\le j<i}w(j+1,i)\),可以猜一下 \(w\) 满足四边形不等式,有了决策单调性就可以做了。
posted @ 2025-07-24 17:43  liruixiong0101  阅读(64)  评论(0)    收藏  举报