[清华营/题解] THUWC2025 题目及题解
THUWC2025 题目及题解
前排提示 : 个人能力有限 , 没有记录工程题的具体题面 . 题解不全 ( 因为我不会 ) .
Day 1 ( 正常场 )
T1
题意
对于一个排列 \(p_n\) , 给出 \(m\) 个限制 , 每个限制包含区间 \([l_i,r_i]\) 和对应的 \(op_i\) , 表示如果 \(op_i=1\) , 要求 \([l_i,r_i]\) 区间单调递增 , 否则要求单调递减 . 给定 \(n\) 和 \(m\) 个限制 , 构造排列 \(p_n\) , 满足最多的限制 .
$n,m\le 5\times 10^5 $ , 时间 \(2s\) , 空间 \(256MB\) , 通过 \(\mathrm{spj}\) 评测 .
题解
考虑先最大化满足的限制数量 , 发现 \([l_i,r_i]\) 是否合法 , 只需考虑填到 \(r_i\) 时最小的满足单调性左端点的 \(l_{lim}\) 与 \(l_i\) 的关系即可 . 而我们可以这样设计状态 : 设 \(f_{i,j,op}\) 表示填到 \(i\) 时 , 满足 \(op\) 对应的单调性的最小左端点是 \(j\) .
转移是容易的 :
一般地 :
考虑从当前位置改变单调性 :
而对于每个 \([l,r],op\) , 更新 :
这个一般式非常符合数据结构优化的要求 , 因此扫描线 \(i\) , 线段树维护 \(j\) , 上述操作都可以线段树维护 .
不过这个问题还没有完全解决 , 因为题目要求的是输出方案 . 那么既然我们是用动态规划解决的 , 就直接记录决策点就好了 .
注意到这个问题具有关键意义的是那些单调性变化的决策点 , 而产生这些决策的时刻是更新 \(f_{i,i,0/1}\) . 记录在 \(i\) 出更新时取的前一个单调性变化的点 \(pos_{i,op}\) , 同时在线段树上记录最优决策的对应下标来更新 \(pos\) 数组.
这样只需顺着 \(pos\) 的记录就可以找出所有单调性变化的点 , 相当于得到了所有相邻元素的大小关系 , 直接拓扑排序即可 .
T2
题意
要求维护一个集合 \(S\) , 顺序给出 \(n\) 个四元组 \((x,y,z,w)\) , 每次询问集合 \(S\) 中满足 \(x\ne x'\) 且 $ y \ne y'$ 且 \(z\ne z'\) 的四元组 \((x',y',z',w')\) 中 , \(w'\) 最大值 ( 没有合法的四元组输出 \(-1\) ) , 并把当前四元组插入 \(S\) .
\(n\le 10^6\) , \(x,y,z,w\le 10^9\) , 时间 \(1s\) , 空间 \(256MB\) .
题解
平凡的想法 , 可以把 \((x,y,z,t)\) , 其中 \(t\) 为时间维 , 视作一个四维偏序 , 这样的做法是 \(n\log^3n\) 的 , 无法通过 .
这道题的特殊性在于偏序的条件是 \(\ne\) , 因此任何一个四元组能覆盖的区域是多的 .
先考虑一个简单问题 , 只有一维 \(x\) 时 , 我们可以直接记录最大和次大 , 最大值所不能覆盖的点只有其本身 , 而次大值的作用就是覆盖最大值没覆盖的点 .
推广到二维 \((x,y)\) . 我们可以把他放在二维平面上考虑 , 发现它无法覆盖的点集是平面上经过 \((x,y)\) 的 , 平行于 \(x\) 轴和平行于 \(y\) 轴的两条直线 . 为了覆盖这些区域 , 我们引入第二个点 , 在不与第一个点重合的条件下 , 它会出现两种情况 :
- 它在这两条直线中的一条上 , 此时两点重合的直线仍然不能被覆盖 , 但是另一条可以了 .
- 它不在这两条直线上 , 把第二个点对应的直线画出来 , 此时所有不能覆盖的位置变成了两个交点 .
我们发现 , 在二维情况下 , 只需 \(\le 4\) 个点 , 就可以保证覆盖整个平面 . 因此去维护这 \(4\) 个点就好了 .
向三维情况推广 . 我们首先可以通过前面的情况 , 加以讨论得到一些基本思路 :
- 一个点不能覆盖的区域的形状是三个方向不同的平面 .
- 多个点不能覆盖的区域的形状是它们无法覆盖的区域的交集 .
- 如果加入一个点 , 只当它使得不覆盖区域缩小了 , 它才有效 .
- 将不覆盖区域缩小为空集需要的有效点个数有限 .
但是三维的形状复杂了很多 , 我们不能逐一情况讨论 , 因此考虑维护不覆盖区域 , 用若干点 , 线 , 面的并集描述它 , 并考虑如何用一个新插入的形状更新这些简单图形 .
-
对于面 :
如果新点的三个面中有一个与他不重合 , 新点的图形对这个面取交后 , 会变成两条交叉直线 .
-
对于线 :
如果它不在新点的三个面中 , 取交后 , 变成一个点 .
-
对于点 :
如果它不在新点的三个面中 , 取交后变为空集 .
初始状态是只有最大值 , 对应三个面 , 也就是说 , 总共的取交后 " 退化 " 的次数为 \(3+6+6=15\) 次 , 而每次插入一个点 , 它有效 , 至少进行了一次退化操作 , 因此点数不超过 \(15\) . 我们完全可以维护所有点 .
具体地 , 考虑我们维护的有效点集是 \(T\) , 查询时 , 直接按权值 \(w\) 从大到小遍历所有点 , 输出第一个合法的点即可 .
插入时 , 考虑整体重构 , 先把点加入集合 , 从大到小重新排序 , 然后按照上面的思路 , 维护点线面的并 , 来判定每个点还留不留在集合里 .
设 "覆盖常数" \(k=15\) , 那么两个操作的复杂度都是 \(O(nk)\) , 可以通过 .
T3
题意
给定一个 01 串 , 定义对它进行一次游戏 :
初始有一个大小为 \(sz\) 的空栈 , \(i\) 从 \(1\) 到 \(n\) , 如果 \(s_i=0\) 就向栈里压入一个元素 , 否则弹出一个元素 . 每次成功弹出一个元素 , 它的价值是 \(16\) , 每次因为当前栈满 , 压入一个元素时压不进去 , 这个元素就被丢掉 , 它的价值是 \(8\) . 最终栈里还剩一些元素 , 他们每个价值是 \(3\) .
给定一个长度为 \(n\) 的 01 串 , \(m\) 次询问 , 给出 \(l,r\) , 对子串 \([l,r]\) 进行游戏 , 要求在 \(0\) 到 \(n\) 中取合适的 \(sz\) , 以最大化价值和 , 输出这个价值 .
\(n ,m\le 5\times 10^5\) .
题解
首先肯定不能直接枚举 \(sz\) , 所以先考虑只询问一次时 , 怎样更高效地找到合适的 \(sz\) , 这需要找一下这个游戏的性质 .
注意到每个元素在不同情况下的价值是给定的 , 我们肯定是希望成功弹出的尽量多 , 留在栈里的尽量少 . 为了留在栈里的尽量少 , 我们希望栈空间越小越好 ; 为了让每个弹出操作能尽量成功弹出 , 则是栈空间越大越好 . 两者矛盾 .
但是发现如果为了减少留在栈里的个数 , 减少了 \(1\) 栈空间 , 造成弹出操作少了 , 相当于收获 \(5\) , 亏损 \(8\) , 必然不合适 .
换句话说 , 能完成弹出操作一定要完成 , 在此基础上取可取的最小空间就好了 .
这样我们就得出了确定栈空间的方式 . 对于只有一个询问的情况 , 可以直接二分栈空间 , 然后模拟就好了 .
现在确定 \(sz\) 的基本策略确定了 , 考虑多次询问 .
我们需要多组询问间有共性的地方 , 因为元素没有本质不同 , 可以把每个弹出操作弹出的元素视为离这个操作最近的 , 没有被前面的操作弹出的元素 ( 这相当于先视栈为无限空间 , 然后找到每个弹出操作的匹配元素 , 这样是满足匹配元素尽可能近 , 让区间内可以实现尽可能完成弹出操作 , 并且最小化栈空间 ) .
而这样操作就可以保证即使区间不同 , 也可以确定哪些弹出操作是成功的 , 同时每个弹出操作要求的最小栈空间也是固定的 .
具体地 , 考虑在位置 \(i\) 插入的元素 , 匹配了位置 \(j\) 的弹出操作 , 它要求的最小栈空间是 \(i\) 到 \(j\) 过程中 , 它在栈中的最大深度 , 而这是可以通过每次插入元素 , 栈的总深度更新时 , 对前面的做区间 \(\max\) 操作 , 然后 \(i\) 的历史最大深度是历史最大总深度减去它所在深度 , 线段树处理 , 可以预处理出每一对匹配对栈空间的要求 .
而这些匹配能贡献到区间 \([l,r]\) 中 , 要求是 \(l\le i, j\le r\) , 做二维数点就可以确定区间的 \(sz\) , 以及成功弹出的次数 .
但是问题还没有结束 , 我们还需要算出最后栈里剩了多少个元素 , 即多少个元素贡献了 \(3\) .
考虑哪些元素能留到最后 , 首先匹配上的显然不会留下来 , 而没留下来的元素 , 相当于其在栈内历史最大深度不超过 \(sz\) . 需要注意到 , 因为区间内的弹出操作匹配的元素不一定在区间内 , 区间内栈的情况和全局不同 , 不方便预处理 .
所以我们换一个思路 , 还是维护所有元素的历史最大深度 , 注意到所有在区间的 \(l\) 前面的点会在走到限制 \(sz\) 的那个弹出操作处被清空 , 所以不用管 , 就是要数所有的历史最大深度小于等于 \(sz\) 且没有被弹出去的点 .
不妨换个思路 , 仍然维护无限空间栈 , 但是每个点停留在其历史最大值处 , 维护每个历史最大值的位置有没有元素 , 这样 , 加点相当于在最浅空位加点 , 去点相当于去最浅点 . 这些同样可以线段树维护 .
( 后面的部分场上没有写出来 , 就大致这个意思 )
T4
题意
给初始有 \(s=0\) , \(a,b\le 256\),可以进行如下三种操作 :
-
\(s\gets s+a\)
-
\(s\gets 2s\)
-
\(a \gets a+b\)
给出 \(a,b,k\) , 长为 \(n\) 的序列 \(w_i\) , 其值域为 \(1/2/3\) , 要求选出 \(w\) 的一个 \(k\) 长子序列,按顺序执行对应编号的操作 , 最大化结果 , 对 \(998244353\) 取模 .
多测 , \(\sum n\le 5\ltimes 10^5 , V\le 10^9\) .
题解
不会.
注意这道题存在一个值域会爆的问题 , 可以取 \(log\) 后先最大化 , 然后记录决策点再算取模后的真实值 .
此外应该有结论 : 对于一个最优决策 , 必定有 3 操作全取前缀 , 2 操作全取后缀 .
此外的留待高人指点吧 .
Day 2 ( 工程题 )
以神经网络为背景出的一道题 , 发的材料是一堆论文 , 没有用 . 理论上 , 只要按照题面的要求逐一模拟就可以拿满 , 当然这很考验程序实现能力 .
下面就不给出范围了 , 都是直接模拟即可 .
实现形式
采用交互题模式 , 你需要实现一个函数 , 其形式可能为 :
void func(const float * A , float *O , int N , int M ){
\\your code
}
其中 func 代表给定的函数名 .
\(A\) 可以代表一个输入矩阵 ( 或向量 ) , 假设 \(A\) 的形状是 \(n\times m\) , 则将实际上的 \(A_{i,j}\) 存储在 \(A_{i\times m+j}\) , 注意下标从 \(0\) 起 .
\(O\) 可以代表一个输出矩阵 , 你需要把运算得到的结果写入 \(O\) 的对应位置 . 存储方式同上 .
\(N,M\) 可以是给出的参数 .
每个子问题会给出具体的函数框架以及对应的意义 .
sub2 matmul
单独说一下这个题吧 , 可能是唯一一个需要自行优化的部分 .
给出大小为 \(n\times k\) 的 \(A\) 和 \(k\times m\) 的 \(B\) , 在 \(O\) 输出 $A\times B $ 的结果 .
注意本题限制为 \(n,m,k \le1024\) , 时间 \(1s\) . 这里需要使用卡常技巧 ( 原题面给出提示了 ) . 把枚举 \(j,k\) 互换 , 发现三个矩阵都满足了内存连续 . 就能通过了 .
其实 , 这道题给出一个启发 , 交换枚举顺序带来的速度快 , 可能要依赖于较好的存储方式 . 连续平摊成一维显然会比二维更连续 .
其余的 sub 都是定义一些矩阵操作 , 然后组合起来 . 注意实现尽量精细 , 控制一下常数和精度就好了 .
个人能力有限 , 还原题面还是有点难了 , 就整理成这样吧 .

浙公网安备 33010602011771号