历届 CSP 刷题记录
\(\texttt{CSP 2019}\)
J 组
\(\texttt{T3}\)
注意到一点:每天卖出纪念品换回的金币可以立即用于购买纪念品,当日购买的纪念品也可以当日卖出换回金币。当然,一直持有纪念品也是可以的。
这告诉我们:在一天内,纪念品就是钱,钱就是纪念品,钱和纪念品没有本质区别,这满足动态规划对于最优化原理和无后效性的要求,可以大胆地购买。
所以可以做如下处理:
把今天手里的钱当做背包的容量
把商品今天的价格当成它的消耗
把商品明天的价格当做它的价值
每一天结束后把总钱数加上今天赚的钱,做 \(t - 1\) 遍完全背包即可。
\(\texttt{T4}\)
题目太过冗杂,转化一下就是:
给定一张无向图,边权都是 \(1\),\(q\) 次询问,每次询问 \(1\) 到 \(a\) 有没有长度为 \(L\) 的的路径。
若递归来做直接 T 飞,其实使用无向图的一个小 tip 就可以快速想出正解。
重要 tip:无向图的每一条边都是一个长度为 \(2\) 的环,所以可以一直在两个点之间转圈圈,再走到终点。
所以先求出 \(1\) 到各点的最短路,若 \(L\le dist_a\),就一定不行,接着再考虑转圈圈能否可以使路径长度为 \(L\)。
注意到环的长度为 \(2\),所以可以从奇偶性的角度来思考。
求出 \(1\) 到各点的长度为奇数和偶数的最短路,偶数为 \(0\),奇数为 \(1\)。若 \(L\) 为奇数且 \(dist[a][1]\le l\) 就行,否则不行,偶数同理。
S 组
\(\texttt{CSP 2020}\)
J 组
\(\texttt{T4}\)
一眼 dp,先确定阶段,因为竖着可以上下走,横着只能往右走,所以将列数作为阶段,放在循环最外层。
但竖着有两个方向,不满足后效性怎么办?
想到了这道题,它也是有两个方向走。
借用这道题的思想,设置一个 \(0/1\) 状态机。
设 \(dp_{i, j, 0}\) 表示从下往上到达 \((i, j)\) 能获得的最大数值,\(dp_{i, j, 1}\) 表示从上往下到达 \((i, j)\) 能获得的最大数值。
接下来就可以写出状态转移方程:
\(dp_{i, j, 0} = \max(dp_{i + 1, j, 0},\max(dp_{i, j - 1, 0}, dp_{i, j - 1, 1})) + a_{i, j}\)
\(dp_{i, j, 1} = \max(dp_{i - 1, j, 1},\max(dp_{i, j - 1, 0}, dp_{i, j - 1, 1})) + a_{i, j}\)
最后的结果即为 \(\max(dp_{n, m, 0}, dp_{n, m, 1})\)。
发现计算 \(j\) 时只会用到 \(j - 1\),所以可以滚动数组滚掉一维。
\(\texttt{update on 2024.10.09:}\)
发现今天刚好是做这道题的一周年,但是挫折重重……
这道题需要处理的细节比较多:
-
第一列和最后一列都只能往下走,所以状态转移方程有些不同,要单独拎出来求;
-
\((1, 2)\) 只能从左径直走来或从下走过来,而其他列的第一个可以从左下走来或、左边径直走来或从下走来,所以应该这么写:
if(i > 2) dp[i][1][1] = max(dp[i - 1][1][0], dp[i - 1][1][1]) + a[1][i];
else dp[i][1][1] = dp[i - 1][1][1] + a[1][i];
而除第一列和最后一列之外每一列的最后一行都可以从左边径直走来、左上走来或从上走来,所以可以这么写:
dp[i][n][0] = max(dp[i - 1][n][0], dp[i - 1][n][1]) + a[n][i];
- 加滚动数组时要注意以下 hack 数据:
1 1
1
把最后一句改成:
printf("%lld", max((ll)a[m][n], max(dp[m & 1][n][0], dp[m & 1][n][1])));
即可。
(一年前的我不会这道题看题解过的)
S 组
\(\texttt{T1}\)
\(\texttt{T2}\)
\(\texttt{CSP 2021}\)
J 组
S 组
\(\texttt{CSP 2022}\)
J 组
S 组
\(\texttt{CSP 2023}\)
J 组
\(\texttt{T4}\)
同余最短路入坑题。
分析题意,发现到达每个点的时间必定是 \(\lambda k + b (b < k)\),因为可以晚 \(k\) 的非负整数倍秒出发,所以解的集合是无限的,不妨考虑集合的划分,换个说法,就是动态规划。
其实这种有关同余的题目见多之后,条件反射根据它来划分集合。具体地,因为 \(b < k\),而又有 \(k\le 100\),所以从此入手,从状态机的角度来思考,可以给每个点设计 \(100\) 种状态。
设 \(dist_{u, r}\) 表示从 \(1\) 号点走到点 \(u\) 的最短时间模 \(k\) 等于 \(r\) 时,最短时间为多少。
这道题最毒瘤的一点就是每条边都有时间限制,其实解决方法也很简单:如果在计算过程中发现当前时间小于这条路的开放时间,那么我们就晚一点出发,使得走到这里时刚好能够通过,形式化地:当走到点 \(u\) 时,当前时间为 \(t\),而 \(t < edge\),那么就晚 \(\mu k\) 秒出发,使得到达 \(u\) 的时间变成 \(t + \mu k\),且满足 \(t + \mu k \ge edge,t + (\mu - 1)k < t\)。
这里有一个细节:虽然边权都是 \(1\),但并不能用 bfs 来扩展,因为根据我们对 \(dist\) 的定义,每次需要贪心地寻找一个时间最小的点来扩展,所以要使用 dijkstra 来扩展。
最终答案就是 \(dist_{n, 0}\)。
最后不要忘了无解输出 \(-1\)。