2022 联赛做题记录 2.0
10.3
2022.10.3 考试 T1 ~ T3
「CF1739E」Cleaning Robot(DP)
题意:
给定一张 \(2\times n\) 的网格,有一些格子是脏的。有一个扫地机器人每次都会选择曼哈顿距离最近的一个格子清理,如果有多个距离最近的就会发生故障。现在你需要先清理一些脏的格子使得机器人不会故障,问最多能保留多少脏的格子。
数据范围:\(2\le n\le 2\times 10^5\)。
首先可以预处理出来 \(nxt_{i,j}\) 表示和 \((i,j)\) 同一行的后一个脏格子,如果没有就是 \(+\infty\)。
然后考虑设计一个 dp:设 \(f_{i,j,k}\) 表示机器人现在在 \((i,j)\),\(1\sim j-1\) 列的格子已经全部被清理,上一次清理的在 / 不在第 \(i\) 行,能保留的最多脏格子。
转移的时候考虑两种情况:
- \(k=0\),那么第 \(i\) 行的下一个格子一定会在 \((i,j+1)\) 之后,第 \(i\oplus 1\) 行的下一个格子一定会在 \((i,j)\) 之后。
- \(k=1\),我们肯定不能选 \((i\oplus1,j+1)\),然后贪心地选择 \((i,j+1)\)(因为距离 \((i,j)\) 为 \(1\) 的合法格子只有这个),下一个就在 \((i\oplus1,j+2)\) 和 \((i,j+2)\) 之后选择。
代码:https://pastebin.ubuntu.com/p/zyhtpWYJZw/。
「CF1738E」Balance Addicts(双指针 + 组合计数)
题意:
定义一个长度为 \(len\) 的序列 \(\{a_{1...len}\}\) 为 平衡的 当且仅当 \(\forall 1\le i\le len,a_i=a_{len-i+1}\)。
给定一个长为 \(n\) 的序列 \(a_{1...n}\),求把它划分成若干个子段,每个子段的和拼接而成的序列是 平衡的 的方案数。
数据范围:\(1\le n\le 10^5,1\le a_i\le 10^9\)。
设 \(f_{l,r}\) 为区间 \([l,r]\) 满足条件的方案数。
考虑分情况讨论:
- 如果区间内全是 \(0\),那么 \(f_{l,r}=2^{r-l}\);
- 如果 \(a_l=a_r=0\),设 \(x\) 为前缀 \(0\) 的个数,\(y\) 为后缀 \(0\) 的个数,那么 \(f_{l,r}=f_{l+x,r-y}\times\sum\limits_{i=0}^{\min(x,y)}\binom{x}{i}\binom{y}{i}\),由范德蒙德卷积可知 \(f_{l,r}=f_{l+x,r-y}\times\binom{x+y}{x}\);
- 否则,求出一个最小的 \(L\) 和最大的 \(R\) 满足 \(a_l+a_{l+1}+\cdots+a_L=a_R+a_{R+1}+\cdots+a_r\):
- 如果找不到,那么 \(f_{l,r}=1\);
- 如果 \([L+1,R-1]\) 中全都是 \(0\),那么 \(f_{l,r}=2^{R-L}\);
- 设 \(c0\) 为 \([L+1,R-1]\) 中前缀 \(0\) 的个数,\(c1\) 为后缀 \(0\) 的个数,那么 \(f_{l,r}=f_{L+c0,R-c1}\times\sum\limits_{i=0}^{\min(c0+1,c1+1)}\binom{c0+1}{i}\binom{c1+1}{i}=f_{L+c0,R-c1}\times\binom{c0+c1+2}{c0+1}\)。
其实画画图,多举几个例子应该都可以理解。
代码:https://pastebin.ubuntu.com/p/W3F5hdBYc2/。
「CF1738F」Connectivity Addicts(交互 + 贪心 + BFS)
题意:
这是一道交互题。
有一张 \(n\) 个点的无向简单图,你只知道每个点的度数,要求给图上的每个点染色,满足:
- 每种颜色形成一个连通块;
- 令 \(s_c\) 为颜色为 \(c\) 的所有点的度数之和,\(n_c\) 为颜色为 \(c\) 的点的个数,那么 \(s_c\le n_c^2\)。
每次询问你可以选择一个节点 \(u\),然后你可以得到与 \(u\) 相连的第 \(k\) 个节点(即 \(u\) 连接的第 \(k\) 条边的另一个端点),其中第 \(i\) 次询问点 \(u\) 时的 \(k=i\)。
你需要使用不超过 \(n\) 次询问来解决这个图染色问题。
数据范围:数据组数 \(T\le 1000,1\le n\le 1000,0\le deg_i\le n-1\)。
神仙题。
考虑将点按照度数从大到小排序,每次取出还未被染色的度数最大的点 \(u\),进行如下流程:
- 维护一个点集 \(S\)。一开始 \(S=\{u\}\)。
- 扫描 \(u\) 的连边 \((u,v)\),
- 如果 \(v\) 已经有颜色,那么将点集 \(S\) 中的点全部染成 \(col_v\)。并且结束当前 BFS。
- 否则就将 \(v\) 加入点集 \(S\)。
- 如果当前过程未结束,就将 \(S\) 中的点全部染成一种新的颜色。
首先,连通的条件肯定是满足的。
其次,因为我们按照度数递减的顺序枚举点,所以就有 \(\max\limits_{col_u=c}\{deg_u\}\le n_c\),进而有 \(deg_u\le n_{col_u}\)。
然后考虑另一个限制条件:
- 如果 \(u\) 相邻的节点全部没有被访问,那么 \(s_c=\sum deg\le deg_u\times(deg_u+1)\le (deg_u+1)^2=n_c^2\)。
- 否则,可以使用归纳法。设 \(s_c'\) 为 \(v\) 所在颜色的度数之和, \(n_c'\) 为 \(v\) 所在颜色的点数。那么肯定有:
- \(s_c'\le (n_c')^2\)。归纳法的条件。
- \(d_u\le d_v\le n_c'\)。前面已经提到过。
- \(s_c=s_c'+\sum deg\le s_c'+d_v\times cnt\le (n_c')^2+n'c\times cnt\le (n_c'+cnt)^2=n_c^2\)。
所以,我们就证明了这个做法的正确性。
代码:https://pastebin.ubuntu.com/p/898tp8MQGw/。
10.4
2022.10.4 考试 T1 ~ T3
2022.9.27 考试 T2 悟(bar)&「SCOI2007」k 短路(二分 + 暴力 Dijkstra + 搜索)
首先可以考虑二分路径的长度 \(mid\),那么我们现在就只关心长度 \(\le mid\) 的路径。
从起点开始拓展路径,保证每次都可以拓展出合法的路径。具体的,不考虑已经走过的点,如果当前路径接上当前点到终点的最短路距离仍然比 \(mid\) 大,那么就不考虑这条路径(即剪枝)。这个可以在搜索的时候使用暴力的 \(\mathcal{O}(n^2)\) Dijkstra 计算。
每次都可以 dfs 出一条合法的路径,每条路径对复杂度的贡献是 \(\mathcal{O}(n^3)\)(因为每个点都会跑一次 \(\mathcal{O}(n^2)\) 的 Dijkstra)。那么总复杂度就是 \(\mathcal{O}(n^3k\log w)\)。
注意输出路径的时候,要把长度 \(\le mid-1\) 的路径先去掉,因为统计的是 \(\le mid\) 的路径数量。
代码:https://pastebin.ubuntu.com/p/mQMD2cXWQK/。
「CF1450E」Capitalism(最短路应用 + 差分约束)
首先可以把条件转化为 \(1\le a_j-a_i\le 1\) 和 \(-1\le a_j-a_i\le 1\)。然而我们忽略了 \(a_j=a_i\) 的情况,这个时候它也是不合法的。
看到绝对值 \(=1\),其实可以想到建图之后应该是一个二分图。否则就会出现奇环,肯定有两个数绝对值相等,不合法。所以直接把有奇环的情况判掉就行了。
然后就可以直接跑差分约束了。如果差分约束无解那么题目肯定无解。
否则,我们考虑固定最小值,找它到所有点的距离的最大值,在所有这种 \(\max-\min\) 的形式中找到极差最大的。
容易发现极差最大的解中每个数(即距离)一定都是非负的。证明考虑调整。
代码:https://pastebin.ubuntu.com/p/MrCQC5gH4D/。
「CF1730E」Maximums and Minimums(并查集 + 单调栈)
题意:
给定一个长度为 \(n\) 的序列 \(a_1,a_2,\dots,a_n\),问有多少个区间满足区间 \(\max\) 是区间 \(\min\) 的倍数。
数据范围:数据组数 \(t\le 10,\sum n\le 5\times10^5,1\le a_i\le 10^6\)。
一开始想了个最值分治 + 二分,有点难写/tuu
实际上有一种巧妙的做法。
先用单调栈求出每个数左边第一个 \(\ge\) 它的数的位置和右边第一个 \(>\) 它的数的位置,这个很方便。
然后考虑从大到小枚举最小值,然后枚举这个最小值的倍数作为最大值。那么我们就是要计算出同时包含最小值和最大值的区间个数。
可以使用容斥,即用 最大值确定合法、最小值大于等于要求的值 的区间数量减去 最大值合法、最小值大于要求的值 的区间数量。
这个可以使用并查集维护区间信息。具体的,每次加入一个数的时候就将它向左右合并,表示这个数已经可以被考虑进贡献中。
可能还是有点没讲清……看代码应该可以理解。
代码:https://pastebin.ubuntu.com/p/96WJqXXyGc/。
10.5
2022.10.5 考试 T1 T2 T4
「AGC013D」Piling Up(DP + 容斥)
首先有一个显而易见的 DP:设 \(f_{i,j}\) 表示已经进行了 \(i\) 次操作,还剩下 \(j\) 个黑球的颜色序列数。转移的时候由于球的总数不变,所以只需要关心黑球 / 白球的有无,讨论四种情况即可。
但是这样会导致算重,因为相同的颜色序列一开始可能会有不同的黑球数。
那么考虑去重,我们只统计途中黑球的最小值刚好为 \(0\) 的那一次操作序列。
可以在 dp 式子里再加一维,同时也可以直接用有 \(n\) 的球的答案减去有 \(n-1\) 个球的答案。
为什么呢?这是因为如果途中黑球最小值不是 \(0\),那么把一开始的黑球数减去 \(1\) 该方案仍然可行。所以所有中间算重的操作序列最后都会被减掉,只会留下一个。
代码很好写:https://pastebin.ubuntu.com/p/DfWjCQ6YNp/。
2022.9.26 考试 T3 夏兜(pockets)& CODE FESTIVAL 2016 Final H - Tokaido(deque + 整体 DP)
设 \(f_i\) 表示当前操作者在 \(i\) 位置,对手在 \(i+1\) 位置时二者最大的分差(当前操作者的分值 - 对手的分值)。
那么当前的操作肯定是在 \(i+1\) 后面选一个位置 \(j\),然后对手把 \([i+1,j-1]\) 全部选掉,即:
其中 \(s_i=\sum\limits_{j=1}^i a_j\)。
记录后缀 \(\max\) 就可以单次询问做到线性。
进一步拆式子。把 \(f_{i+1}\) 带进去,化简可以惊奇地发现 \(f_i=|a_{i+1}-f_{i+1}|\)!
那么考虑整体 DP。设 \(g_{i,j}\) 表示以 \(j\) 为初始值,从 \(i\) 开始往前扫得到的最终值。转移形如 \(g_{i,j}=g_{i+1,|a_i-j|}\)。
很显然,这个 DP 的形式大概就是把 DP 数组向右移 \(a_i\) 位,然后再把原先的 \(g_{1\dots a_i}\) 翻转一下插到前面(打一个表也可以发现)。可以用一个 deque 来维护。
代码:https://pastebin.ubuntu.com/p/sj5SfyNs5c/。
10.6
2022.10.6 考试 T1 T2
LOJ 上下界网络流模板
10.7
2022.10.6 考试 T3
「CF685E」Travelling Through the Snow Queen's Kingdom(Floyd + 倒序加边)
题意其实应该是:如果在 \(\le i\) 的时刻经过第 \(i\) 条边,那么走出第 \(i\) 条边的时间就是 \(i\) 时刻。
考虑倒过来加边,记录 \(f_{i,j}\) 表示从 \(i\) 到 \(j\) 经过的最大边权最小是多少。那么加入这条边 \((u,v)\) 之后影响的 \(f_{i,j}\) 一定满足 \(i=u/i=v/j=u/j=v\),更新可以做到一次 \(\mathcal{O}(n)\)。
那么把询问离线,挂在 \(l\) 位置上,可以直接判断能否走到。
代码:https://pastebin.ubuntu.com/p/kPspGTZr5S/。
10.8
2022.10.8 考试 T1 /「CF1707E」Replace(ST 表 + 倍增)
考虑倍增,设 \(f_{i,j,k}\) 表示 \([i,i+2^j-1]\) 跳 \(2^k\) 步所得到的区间的左端点,\(g_{i,j,k}\) 为对应的右端点。
首先可以预处理出所有 \(f_{i,j,0}\) 和 \(g_{i,j,0}\),这就是一个区间最值。
然后,发现区间是可合并的,那么就都可以 ST 表预处理出来。
注意 cache miss 和答案上界(其实是比 \(n\) 大的)!!!!!
关键还是在于发现可合并性。
代码:https://pastebin.ubuntu.com/p/Bfpzyk9rj2/。
10.9
Codechef November Lunchtime 2020 部分题目
题解见 https://www.luogu.com.cn/blog/jiangly/codechef-november-lunchtime-2020-post。
2022.10.8 考试 T2
10.10
2022.10.10 考试 T1 T2
2022.10.8 考试 T3
「CF1559D2」Mocha and Diana (Hard Version)(性质 + 并查集)
连通块数量较少的那一个森林最后肯定会变成一个连通块。证明考虑对于另一张图两个连通块中的点,它们在这张图中肯定是连通的,进而推出所有点都是连通的。
那么我们可以先随便一个点,把能连的边都连了。令 \(L\) 为第一张图中与这个点不连通的点集,\(R\) 为第二张图与这个点不连通的点集,那么 \(L\) 与 \(R\) 肯定不交。然后就可以把 \(L\) 和 \(R\) 中的点两两配对,这样肯定合法,因为它们在两张图中与这个点的连通性相反。
代码:https://pastebin.ubuntu.com/p/d95Nx2Pb9Q/。
10.11
2022.10.11 考试 T1 T2
「CF1109D」Sasha and Interesting Fact from Graph Theory(组合计数 + 扩展 Cayley 定理)
枚举 \(a\to b\) 路径上除开这两个点的点数 \(i\),那么就有 \(i+1\) 条边,边权 \(\in[1,m]\) 且总和为 \(m\),插板法可以知道这是 \(\binom{m-1}{i}\)。选择点的方案 \(\binom{n-2}{i}\),可以任意排列所以还要乘 \(i!\),其他边边权任意 \(m^{n-1-(i+1)}\)。然后就相当于把这 \(n\) 个点分成 \(i+2\) 个连通块,且前 \(i+2\) 个点属于不同连通块,根据扩展 Cayley 定理可知方案数为 \((i+2)n^{n-(i+2)-1}\)。那么答案就是 \(\sum\limits_{i=0}^{n-3}\binom{n-2}{i}\binom{m-1}{i}i!m^{n-1-(i+1)}(i+2)n^{n-(i+2)-1}+\binom{m-1}{n-2}(n-2)!\)。后面的是因为 \(i=n-2\) 需要特判。
代码:https://pastebin.ubuntu.com/p/GCBm3mNDF9/。
2022.10.10 考试 T3
「CF1672H」Zigu Zagu(思维 + 前缀和)
记 \(x\) 为当前串中相邻两项均为 0 的数量,\(y\) 为当前串中相邻两项均为 1 的数量。
那么每一次如果选择偶数长度的串删掉,\(x\) 和 \(y\) 都会减少 \(1\)。并且如果 \(x>0\) 且 \(y>0\),总可以找到这样一个串。
如果选择奇数长度的串删掉,\(x\) 或 \(y\) 会减少 \(1\)。
经过分析,答案为 \(\max(x,y)+1\)。前缀和即可。
代码:https://pastebin.ubuntu.com/p/WhJyKWYGNw/。
10.12
「CF1741G」Kirill and Company(状压 DP + 最短路图 + 拓扑排序)
题意:
Kirill 住在一张 \(n\) 个点 \(m\) 条边的连通无向图上的 \(1\) 号节点。
他有 \(f\) 个朋友,第 \(i\) 个朋友住在 \(h_i\) 位置上。其中有 \(k(k\le 6)\) 个朋友没有车,分别为第 \(p_1\sim p_k\) 个,他们只能步行或者搭别人的车回去。一个有车的人能搭载任意数量的没车的人回到他们的家,但要保证他走的是从 \(1\) 号节点到他的家的最短路。
问最少有多少个人需要步行回家。
数据范围:\(2\le n\le 10^4,n-1\le m\le \min(10^4,\frac{n(n-1)}{2}),1\le f\le 10^4,2\le h_1\le n,1\le k\le \min(6,f),1\le p_i\le f,p_i<p_{i+1}\)。
建出最短路 DAG,那么只能在这张 DAG 上转移。
设 \(t_{i,s}\) 表示走 \(1\to i\) 的最短路,搭载没车的人的集合为 \(s\) 是否可行。这个可以记录每个节点上没车的人的集合,然后枚举子集转移。
再设 \(f_{i,s}\) 表示考虑前 \(i\) 个人,已经有车回去的人的集合为 \(s\) 是否可行。转移同样是枚举集合。
代码:https://pastebin.ubuntu.com/p/bvSp5c5w7n/。
「CF1473E」Minimum Path(最短路)
注意到如果任意减去一条边的边权,那么肯定减去边权最大的最优。如果任意给一条边的边权翻倍,那么肯定选择边权最小的。
那么只要记 \(dis_{i,0/1,0/1}\) 表示走到 \(i\),是否减去了一条边的边权,是否翻倍了一条边的边权的最短路。
正常跑 Dijkstra 即可。
代码:https://pastebin.ubuntu.com/p/dQwGpqzvjj/。
「CF1736E」Swap and Take(DP)
题意:
给定一个长度为 \(n\) 的数组 \(a\),你需要在上面做 \(n\) 次操作。
一开始你的分数是 \(0\)。
在第 \(i\) 次操作中,你可以交换数组中相邻的两个元素并且将其中一个变成 \(0\) 或者保持原数组不动。无论你是否操作,分数都会加上 \(a_i\)。
问你能得到的最大分数。
数据范围:\(2\le n\le 500,1\le a_i\le 10^6\)。
首先,我们肯定不可能把 \(0\) 加入答案中。
假设第 \(i\) 次操作加上的分数是 \(a_{p_i}\),那么肯定有 \(p_i\le p_{i+1}\)。因为如果出现 \(p_i>p_{i+1}\) 就肯定是把后面的交换到了前面,当前选的就会变成 \(0\)。
那么设 \(f_{i,j,k}\) 表示到第 \(i\) 轮,\(p_i=j\),已经进行了 \(k\) 次交换操作所能得到的最大分数。
转移可以分类讨论:
- 若 \(p_i=p_{i-1}\),相当于将上一位和这一位交换,并将这一位变成 \(0\),方程为 \(f_{i,j,k}\leftarrow f_{i-1,j,k-1}+a_j\)。
- 若 \(p_i>p_{i-1}\),就相当于之前进行了一些交换操作把 \(p_i\) 移到了 \(i\),需要 \(p_i-i\) 次。枚举 \(p_{i-1}\),方程为 \(f_{i,j,k}\leftarrow\max\limits_{x<j}(f_{i-1,x,k-(j-i)})+a_j\),前缀和优化即可。
这题的巧妙就在于将交换操作和贡献计算分开处理,将操作次数单独做一个 DP 维度。
代码:https://pastebin.ubuntu.com/p/xQhxdrmXsW/。
2022.10.11 考试 T4
「CF1582F2」Korney Korneevich and XOR (hard version)(DP 优化)
显然的想法是直接设 \(f_{i,j}\) 表示当前最后一位是 \(i\),异或和能否等于 \(j\)。其实可以进一步优化,变成 \(f_i\) 表示异或和等于 \(i\),最后一位最小是多少。转移显然,应该能过 F1。
回到最初的 DP 上来。一般来说,这种 DP 数组值为 bool 变量都比较浪费。
换一换思路:设 \(f_{i,j}\) 表示最后一位 \(<i\),异或和能否为 \(j\)。那么转移到一个 \(a_i\),只要 \(\exist k\le a_i, f_{k,j}=1\),就说明所有 \(f_{l,j\oplus a_i}(l>a_i)\) 都是 \(1\)。也就是说,对于每一个 \(i\),\(f_{l,i}=0\) 的所有 \(l\) 一定构成了一段前缀。
记 \(mx_i\) 表示满足 \(f_{1\sim mx_i,i}\) 都是 \(0\) 的最大的值,开一个 vector \(b_i\) 表示所有结尾 \(<i\) 的递增子序列可能的异或和。每次枚举到一个数 \(a_i\) 的时候,\(b_{a_i+1\sim MAX}\) 都可以插入 \(b_{a_i,x}\oplus a_i\)。每次都枚举到 \(MAX\) 太浪费,可以只枚举到 \(mx_{b_{a_i, x}\oplus a_i}\)(\(x\) 就是 \(b_{a_i}\) 的一个元素下标),因为之后的已经存了。
可能还是不太清楚,详见洛谷题解。
代码:https://paste.ubuntu.com/p/fxJJvrFz3n/。
10.13
2022.10.13 考试 T1 T2
「CF321E」Ciel and Gondolas(分治决策单调性)
交叉 \(\le\) 包含,所以贡献形式满足四边形不等式。应该很好证明。
设 \(f_{i,j}\) 表示前 \(i\) 个人分了 \(j\) 艘船的最小沮丧值。分治转移即可。
代码:https://paste.ubuntu.com/p/VMgPjMhqTt/。
10.14
2022.10.13 考试 T3
「CF1739F」Keyboard Design(AC 自动机 + DP)
考虑对于每个串,建立一张 \(12\) 个点的图,两个点之间有边当且仅当它们在这个串中相邻。
如果一个串可能在最后有贡献,那么肯定图的形态是一条链。因为有环或者度数 \(\ge 3\) 的点肯定不行。
链有正反两种形式,我们把它们都插入一个 AC 自动机里面。因为它们不可能同时贡献。
AC 自动机上每个节点记录以这个点结尾的前缀和所有后缀可能贡献之和。注意在建 AC 自动机的时候需要把这个转移,不要忘了。
然后就设 \(f_{i,j}\) 表示到第 \(i\) 个节点,这 \(12\) 个字母的选择情况是 \(j\)(一个二进制数)的最大答案。转移枚举儿子即可。
输出方案直接记录每个状态的前驱。
代码:https://pastebin.ubuntu.com/p/dcgNWdh88G/。
10.15
2022.10.15 考试 T1 T3
10.16
「CF1746C」Permutation Operations(差分 + 性质)
题意:
给定一个长度为 \(n\) 的排列 \(a\),你需要进行 \(n\) 次操作,第 \(i\) 次操作需要选择一段后缀 \(+i\)。问如何操作才能使最终序列的逆序对数量最少。
数据范围:\(1\le n\le 10^5\)。
场上看错题还能过 Pretests,佛了。掉大分。
大胆猜想一波最后肯定可以将序列变成不降(即逆序对数量为 \(0\))。
其实是可以做到的:把序列变成差分序列,即 \(d_i=a_{i+1}-a_i\),那么最终序列不降等价于 \(\forall 1\le i<n,d_i\ge 0\)。那么因为 \(a_{i+1}>0\),所以 \(d_i>-a_i\),只需要把 \(d_i\) 加上 \(a_i\) 就行,即在第 \(a_i\) 次操作选择 \([i+1,n]\) 这段后缀。
代码:https://pastebin.ubuntu.com/p/475KF735QB/。
「CF1746F」Kazaee(随机化 + 树状数组)
题意:
给定一个长度为 \(n\) 的序列,有 \(q\) 次操作如下:
1 i x,将 \(a_i\) 变成 \(x\);2 l r k,查询 \([l,r]\) 中是不是每个数都出现了 \(k\) 的倍数次。数据范围:\(1\le n,q\le 3\times10^5,1\le a_i,x\le 10^9,1\le k\le n\)。
关于出现次数,一定要想到随机权值!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
这题中,我们可以先离散化,把值域变小,然后给每个数赋上一个随机的权值。修改直接树状数组;出现次数为 \(k\) 的倍数的条件就可以使用区间和是否为 \(k\) 的倍数来判断,也可以用树状数组。因为一次正确率可能没有那么高,所以可以多做几次,每次都满足条件才说明这个区间满足条件,\(30\) 次差不多了。
代码:https://pastebin.ubuntu.com/p/MTmZhHc6wd/。
「CF799F」Beautiful fountains rows(随机化 + 前缀和)
很神奇的一道题……
首先同样可以给每一行的 1 分配一个随机权值,如果一行中某个区间内有奇数个,那么区间内的异或和就不为 \(0\),否则就是 \(0\)。
显然为 \(0\) 比有值好处理得多。于是考虑转换限制,即有奇数个 1 的时候区间异或和为 \(0\),有正偶数个 1 的时候区间异或和为 \(1\)。这样也可以处理掉区间内没有 1 的情况。
怎么做到呢?可以让 \(l_i\) 不异或随机权值,并且把询问的 \(a\leftarrow a+1\),即区间变成 \((a+1,b)\) 。证明考虑分类讨论 \(a\) 这个位置是不是 1。
很明显可以把所有行某一列的信息整合起来,即记一个 \(x_i\) 表示所有行第 \(i\) 列的权值异或和,\(s_i\) 表示 \(x\) 的前缀异或和。那么 \((a,b)\) 合法就等价于 \(s_a=s_b\)(因为我们已经把区间的左端点 \(+1\))。
这个可以通过开一个 map 实现求解。
全部为 \(0\) 的情况可以在最后减掉。
代码:https://pastebin.ubuntu.com/p/yRZFy3NtHQ/。
10.17
2022.10.17 考试 T1 T2
「CF908G」New Year and Original Order(数位 DP + 拆贡献)
考虑把每一个数值的答案拆开之后再加起来。
因为最后的贡献中数位是不降的,所以可以考虑把贡献拆成不超过 \(9\) 个形如 \(111...1\) 的形式。
关键一步:第 \(i\) 个拆开的数中 \(1\) 的个数其实就是 \(\ge i\) 的数位个数。
比如,\(125=111+11+1+1+1\)。
那么就可以先枚举每一个数码,设 \(dp_{i,j,0/1}\) 表示已经确定了前 \(i\) 位,有 \(j\) 位 \(\ge\) 当前枚举的数码,是否顶着上界的方案数。转移枚举这一位填什么即可。最后加入答案的时候枚举贡献中有多少个 \(1\),数值乘上方案数就是最终贡献。
代码:https://pastebin.ubuntu.com/p/W5NsS5fn6t/。
10.18
2022.10.18 考试 T1 T2
2022.10.15 考试 T2
10.19
「SDOI2014」数数(AC 自动机 + DP)
设 \(f_{i,j,0/1}\) 表示已经填了前 \(i\) 位,到达了节点 \(j\),是否顶上界的合法数字个数。
转移的话可以直接枚举下一位,或者新开一位。
注意如果一个节点 \(fail\) 树上的祖先有不能走的,那么它也不能被走到。因为 \(fail\) 的含义就是前缀的一个后缀。
代码:https://pastebin.ubuntu.com/p/3HgBDkK3mK/。
2022.10.18 考试 T3
CodeChef March Lunchtime 2022 Division 1 A~E
比赛链接:https://www.codechef.com/LTIME106A。
官方题解:https://discuss.codechef.com/search?expanded=true&q=tags%3Aeditorial%2Bltime106。
A - Adjacency Love
题意:
给出 \(n\) 个数 \(a_{1\sim n}\),问能否重新排列这些数,使得 \(\sum\limits_{i=0}^{n-1}a_i\times a_{i+1}\) 为奇数。构造方案,多组数据。
数据范围:\(1\le T\le 1000,2\le n\le 500,1\le a_i\le 10^6\)。
只有两个奇数放一起才可以改变和的奇偶性。
先把所有奇数放一起,偶数放一起,如果不合法就把最后一个奇数和第一个偶数交换位置。正确性显然。
代码:https://paste.ubuntu.com/p/Q9KPxwt4mB/。
B - Winter
题意:
给定一张 \(n\) 个点 \(m\) 条边的无向图,有 \(Q\) 次操作,每种操作是下面三种类型之一:
1 u,将 \(u\) 打上标记;2 t,表示经过了 \(t\) 时刻;3 u,查询当前时刻 \(u\) 是否被打上了标记。如果一个点 \(u\) 在 \(t\) 时刻有标记,那么在 \(t+1\) 时刻所有和 \(u\) 有边相连的点都会被打上标记。
数据范围:\(1\le n,Q\le 10^5,1\le m\le 3\times 10^5\)。
暴力题,直接模拟这个过程就行了!
具体的,如果操作 \(1\) 中一个点之前没有标记,就把它加入队列;操作 \(2\) 直接暴力进行扩展,直到不能扩展出新的点被标记或者时间已经走完了;操作 \(3\) 就直接查询。
代码:https://paste.ubuntu.com/p/SHPx48vnqm/。
C - Separate Them All
题意:
给定一棵 \(n\) 个节点的树,你需要破坏所有道路,破坏道路 \((u,v)\) 的代价是 \(d_ud_v\),其中 \(d_u\) 指的是点 \(u\) 当前的度数(即和 \(u\) 还有边相连的点的个数)。问破坏所有道路的最小代价。
这个问题太简单了,于是你还有 \(Q\) 次修改,每次修改删去边 \((a,b)\) 加入边 \((c,d)\),问修改之后破坏所有道路的最小代价。修改之间独立。
多组数据。
数据范围:\(1\le T\le 10^4,1\le \sum n,\sum Q\le 10^5\)。
大胆猜测一波直接剥叶子是对的!证明大概可以考虑邻项交换?
那么总贡献就是 \(\sum\limits_{i=1}^n[d_i>1]\frac{d_i(d_i+1)}{2}+\sum\limits_{i=1}^n[d_i=1]-1\)。
这两个东西都可以实时维护。
代码:https://paste.ubuntu.com/p/rC7SXbNp7m/。
D - Mathology
题意:
给定一个长度为 \(n\) 的序列 \(A\),\(Q\) 次询问在 \(A_{l\sim r}\) 中选择一个长度 \(>1\) 的子序列使得这个子序列所有数的 \(\gcd\) 最大,问最大是多少。多组询问。
数据范围:\(1\le T\le 10^5,2\le \sum n\le 10^5,1\le \sum Q\le 10^5\)。
显然只会选两个数。
考虑把询问离线下来,将右端点从左向右扫过去。
维护一个单点 \(\max\)、后缀 \(\max\) 的树状数组,每次加入一个数的时候,枚举它的因数 \(j\),如果 \(j\) 之前已经是某个数 \(a_x\) 的因数了,就在树状数组的 \(x\) 处和 \(j\) 取 \(\max\)。这个意思是说,左端点在 \(x\) 之前的询问答案一定 \(\ge j\)。
记得多测清空!!!!
代码:https://paste.ubuntu.com/p/y978KvrFtT/。
E - Subsequence
题意:
给定一个长为 \(n\) 的序列 \(A\) 和一个正整数 \(m\),保证 \(A\) 中元素两两不同。
对于每一个 \(k(1\le k\le m)\),问 \(A\) 有多少个子序列 \(A_{i_1},A_{i_2},A_{i_3},\dots,A_{i_p}\) 满足:
- \(\sum\limits_{j=1}^p A_{i_j}=k\)。
- 设 \(f(l,r)=\max\limits_{i=l}^r A_i\),那么 \(\forall 1\le j<p,f(i_j,i_{j+1})>\max(A_{i_j},A_{i_{j+1}})\)。如果 \(p=1\) 那么这条限制也满足。
答案对 \(998244353\) 取模。多组数据。
数据范围:\(1\le T\le 10^3,1\le n,\sum n\le 2000,1\le A_i\le \sum m\le 8000,n\le m\)。
一个很显然的暴力 DP:设 \(f_{i,j}\) 表示最后一个选的是 \(i\),和为 \(j\) 的方案数。
转移:\(f_{i,j}=\sum\limits_{k<i-1}[\max\limits_{l=k+1}^{i-1}A_l >\max(A_k,A_i)]f_{k,j-A_i}\)。
我们来分析一下不能成功转移的条件:即 \(\max\limits_{l=k+1}^{i-1}A_l<\max(A_k,A_i)\)。
- 若 \(A_k<A_i\),那么设 \(x\) 是 \(i\) 前第一个比 \(A_i\) 大的位置,则不能转移的 \(k\) 应该满足 \(x\le k<i-1\)。
- 若 \(A_k>A_i\),那么构造出一个递减的单调栈,则在栈中的元素都不能转移。
因此,设 \(l_i\) 为单调栈中 \(i\) 前一个位置,那么所有 \(1\le k\le l_i\) 且不在单调栈中的元素都可以被转移。
这个可以记录两个前缀和解决。
代码:https://paste.ubuntu.com/p/5m73xJFZNc/。
「CF1743F」Intersection and Union(DP + 矩阵乘法 + 线段树)
考虑对每一个位置算贡献,即求出它在最终集合中的操作序列方案数。
这个可以用简单 DP 求:设 \(f_{i,0/1}\) 表示考虑了前 \(i\) 个集合,当前位置在不在当前的答案集合中的方案数。
转移:
- 若第 \(i+1\) 个集合包含了当前位置,那么:
- \(f_{i+1,0}=f_{i,0}+f_{i,1}\);
- \(f_{i+1,1}=f_{i,0}\times 2+f_{i,1}\times 2\)。
- 若第 \(i+1\) 个集合没有包含当前位置,那么:
- \(f_{i+1,0}=f_{i,0}\times 3+f_{i,1}\)。
- \(f_{i+1,1}=f_{i,1}\times 2\)。
如果我们对于每一个位置都 \(\mathcal{O}(n)\) 再算一遍,未免有些太浪费了。
因为每次转移的系数是一样的,所以考虑把操作写成 \(2\times 2\) 矩阵的形式,使用一棵线段树维护区间矩阵乘积。矩阵中的 \(A_{i,j}\) 表示 \(f_{*,i}\to f_{*,j}\) 的转移系数。
扫一遍每个位置,在当前位置是某个区间的左端点的时候将这个区间对应的线段树下标的矩阵改成包含当前位置的转移矩阵,是某个区间右端点 \(+1\) 的时候将矩阵改成不包含当前位置的转移矩阵。
因为矩阵乘法满足结合律,所以我们只需要知道当前位置是否在第一个区间中就可以算出最终的答案。
代码:https://pastebin.ubuntu.com/p/XnPDBTxC2H/。
10.20
「洛谷 P2123」皇后游戏(贪心 + 邻项交换)
orz 大头,已经写得很明白了。
2022.10.20 考试 T3
「SDOI2016」储能表(数位 DP)
考虑直接计算 \(i\oplus j\ge k\) 的数量以及所有满足条件的 \(i\oplus j\) 的和。
这个可以直接数位 DP:设 \(f_{i,0/1,0/1,0/1}\) 和 \(g_{i,0/1,0/1,0/1}\) 分别表示考虑到第 \(i\) 位,是否顶到了 \(n,m,k\) 的上界,满足条件的方案数以及 \(i\oplus j\) 之和。转移直接枚举下一位是什么即可。
可以先求出 \(f\) 数组,然后再去算 \(g\) 数组。因为 \(g\) 的转移是依赖于 \(f\) 的。
这种记两种 DP 数组的方法很巧妙!
代码:https://pastebin.ubuntu.com/p/5KPw9hQ3fN/。
「ICPC World Finals 2018」绿宝石之岛(概率期望 DP)
首先,所有的操作个数有 \(n(n+1)\dots(n+d-1)\) 种,即 \(\frac{(n+d-1)!}{(n-1)!}\)。
考虑一种最终局面 \(a_i\),其中 \(a_i\) 表示第 \(i\) 个人新拿的绿宝石个数,\(\sum a_i=d\),得到它的操作序列个数。为 \(\frac{d!}{\prod a_i!}\prod a_i!=d!\),前者是可重集排列,后者是每个人当前可以选择手上哪一个宝石分裂。因此,我们可以得出结论:每种最终局面的对应方案数是相同的!
接下来,对于 \(a_i\) 按层从小到大进行 DP:设 \(f_{i,j}\) 表示 \(i\) 个人,此时 \(\sum a=j\) 的方案数,\(s_{i,j}\) 表示 \(i\) 个人,此时 \(\sum a=j\) 的所有前 \(r\) 大的 \(a\) 的和。
转移:
可以解释为在这 \(i\) 个人中选择 \(k\) 个出来 \(+1\)。
最终答案也是好求的。
注意要开 long double!
代码:https://pastebin.ubuntu.com/p/sNHqt42gnw/。
10.21
「CF870E」Points, Lines and Ready-made Titles(建图 + 二分图)
建一个二分图,左部点是所有横坐标,右部点是所有纵坐标,一个点 \((x,y)\) 就相当于将左边的点 \(x\) 和右边的点 \(y\) 连起来。
考虑对于每一个连通块分开考虑,再利用乘法原理将它们的答案乘起来。
如果一个连通块是一棵树,那么设 \(cnt\) 为其点数,则答案为 \(2^{cnt}-1\);否则答案为 \(2^{cnt}\)。
考虑每个左部点如果是 \(1\),就相当于有一条竖直的直线;右部点如果是 \(1\),就相当于有一条水平的直线。如果是一棵树,除了全都是 \(1\) 的情况都可以出现;如果不是一棵树,那么所有情况都能出现。
代码:https://pastebin.ubuntu.com/p/D2W3T9ThgV/。
「CF1749E」Cactus Wall(最短路 + 性质 + 01BFS)
一个重要的观察就是:条件等价于存在一条从第一列到最后一列的仙人掌路径。这条路径中每个点应该在前一个点的右上角或右下角。证明显然。
所以直接建图跑 01BFS / Dijkstra 即可。
因为是口胡的,所以没有代码。
「CF1749F」Distance to the Path(树上差分 + 树状数组)
考虑如果一个点被一条路径贡献到,那么它肯定是在某个节点的子树中,或者是距离两端点的 LCA 不超过 \(d\)。
我们可以将 LCA 向上的 \(d\) 个祖先也加入到这条路径的贡献中。
因为 \(d\le 20\),所以我们求答案时暴力跳祖先算贡献。如果一条路径对一个点有贡献,那么跳祖先的时候肯定会和这条路径有交点。因为不能重复计算,所以我们需要找到一个不会算重算漏的点。我们给每个点设一个标记,令每个路径上的点标记为 \(d\),LCA 的第 \(i\) 个祖先的标记为 \(d-i\)。那么当我们跳到第 \(j\) 个祖先的时候,如果路径某个点的标记也为 \(j\),说明这个点可以被这条路径贡献到。但如果直接这样算可能会算漏,需要将 LCA 的第 \(i\) 个祖先同时标记上 \(d-i-1\),这是为了避免在第 \(i+0.5\) 个祖先处相交的情况。
原路径的贡献可以树上差分 + 树状数组,LCA 祖先的贡献可以直接打标记。
代码:https://pastebin.ubuntu.com/p/Whq6nSh6f8/。
「CF547E」Mike and Friends(AC 自动机 + fail 树 + 树状数组)
首先套路地建出 AC 自动机和 fail 树,然后把询问差分成两个前缀。
每扫过一个字符串就将走过的节点权值全部 \(+1\),查询就相当于查某个字符串结尾节点的子树内节点权值和。
这个可以 dfs 序 + 树状数组维护。
代码:https://pastebin.ubuntu.com/p/TPwYsFVxZC/。
「CF587F」Duff is Mad(AC 自动机 + 根号分治 + 树状数组)
首先套路地建出 AC 自动机和 fail 树。
考虑如何刻画这个答案:实际上就是所有 \(l\sim r\) 结尾位置的子树内,所包含的串 \(k\) 的节点个数之和。
设 \(m=\sum|s_i|\)。
直接做不太好做,考虑根号分治,设一个阈值 \(B\),则:
- 对于 \(|s_i|>B\),这种串不会超过 \(\frac{m}{B}\) 个,可以对每个串处理。直接扫一遍 \(k\) 的每个位置,将走到的 fail 树上的位置权值 \(+1\),然后暴力查询子树内节点权值和。可以使用前缀和优化做到 \(\mathcal{O}(\frac{m^2}{B}\times\log m)\)。
- 对于 \(|s_i|\le B\),可以 \(\mathcal{O}(|s_i|)\) 来处理。将询问差分成 \(1\sim r\) 和 \(1\sim l-1\) 两个前缀,然后从 \(1\sim n\) 依次加入字符串。加入的时候,将其结尾节点的子树内的所有节点权值 \(+1\),查询的时候直接把每个走到的位置的权值加起即可。时间复杂度 \(\mathcal{O}(qB\log m)\)。
把两种暴力拼起来就行。
代码:https://pastebin.ubuntu.com/p/K8sGf5mHTv/。
10.22
2022.10.22 考试 T1
「CF840E」In a Trap(分块)
先考虑序列上怎么做,即求 \(\max\limits_{i=l}^r\{a_i\oplus (r-i)\}\)。朴素地,把右端点从左往右移动,以某种方式维护答案,把询问挂在右端点上回答。如果每步向后走 \(S\) 步,那么原来维护的 \(a_i\oplus dis\) 就会变成 \(a_i\oplus(dis+S)\)。考虑到异或和加法没有什么优美的性质,所以我们希望能够变成同一种运算。并且由于步长 \(S\) 固定,所以我们可以看成固定右端点 \(r\),向左每 \(S\) 个分一块。
注意到设 \(i\) 到其所在块尾的距离为 \(d_i\),那么 \(a_i\oplus dis=a_i\oplus(d_i+D\times S)\),其中 \(D\) 是 \(i\) 和 \(r\) 之间的块数。并且有 \(0\le dis<S,0\le D\le \frac{n}{S}\)。如果我们把 \(S\) 设成某个 \(2^k\),那么 \(d_i+D\times S=d_i\oplus(D\times S)\)。为了平衡复杂度取 \(S=2^8=256\)。
如果我们已经分好了块,那么考虑维护若干个分块类型,记 \(f_{i,j}\) 表示如果已知 \(D=j\),以 \(i\) 为块尾的大小为 \(S\) 的块中最大的 \(a_x\oplus d_x\oplus(D\times S)\)。最大异或这种东西可以想到 Trie 树。因为 \(D\times S\) 的后面 \(8\) 位都是 \(0\),所以可以把 \(a_x\) 的前面 \(8\) 位放到 Trie 树里,并且记录一个 \(mx_y\) 表示 \(a\) 的前面 \(8\) 位是 \(y\) 的时候最大的 \(a\) 后面 \(8\) 位 \(\oplus d\)。
化简一下式子,设 \(b_i=\lfloor\frac{a_i}{256}\rfloor,c_i=a_i\bmod 256\),那么 \(a_x\oplus d_x\oplus(D\times S)=((b_x\oplus D)\times S)+(c_x\oplus d_x)\)。将 \(i\) 这一块的所有 \(b\) 放进 Trie 树里,\(mx_y\) 表示 \(b_i=y\) 的所有 \(i\) 中最大的 \(c_i\oplus d_i\)。这个时候可以在 Trie 树中查询异或 \(j\) 得到的最大值来求出 \(f_{i,j}\)。那么固定询问右端点,向左扫每一块,整块答案可以用 \(f\) 数组得到,散块可以暴力。
扩展到树上,只要将每个点向上的 \(S\) 个祖先分到一块就行了。和序列上差不多。
代码:https://paste.ubuntu.com/p/JtHPnxhWcM/。
10.23
「CF1753D」The Beach(最短路 + 图论建模)
考虑每个位置就是一个点,它表示的意义是这个位置变成空位的最小花费。
那么如果一个位置一开始就是空位,则最小花费是 \(0\)。
否则,如果一个位置上有床,那么分类讨论:
- 如果将床平移,那么这个位置变成空位首先需要床在这个方向上的下一个位置是空位。即如果床在 \((1,1)\) 和 \((1,2)\),那么 \((1,1)\) 变成空位的前提是 \((1,3)\) 变成空位。所以由 床在这个方向上的下一个位置 向这个位置连一条代价为 \(q\) 的单向边。在这个例子里就是 \((1,3)\) 向 \((1,1)\) 连代价为 \(q\) 的单向边。
- 如果将床旋转,同理,由 这个位置在移动之后变成的位置 向 这个位置 连代价为 \(p\) 的单向边。即如果床在 \((2,2)\) 和 \((1,2)\),那么如果固定 \((2,2)\) 不动,就由 \((2,1)\) 和 \((3,1)\) 分别向 \((1,2)\) 连代价为 \(p\) 的单向边。
从原始空位开始跑多源 Dijkstra,枚举最后床摆放的位置,求出最小代价即可。
代码:https://pastebin.ubuntu.com/p/S77CxkfWh3/。
10.24
2022.10.22 考试 T2 T3
「CF235D」Graph Game(基环树 + 概率期望)
先考虑树的情况怎么做。
由期望的线性性,我们枚举两个点 \((u,v)\),计算 \(u\) 在 \(v\) 的点分树子树中的概率,相加就是期望。
不难发现条件等价于 \(v\) 是 \((u,v)\) 树上路径中最早被选择的点,概率为 \(\frac{1}{dis(u,v)}\)。其中 \(dis(u,v)\) 为 \(u\) 到 \(v\) 路径上的点数。
考虑扩展到基环树。
如果 \(u\) 和 \(v\) 属于同一子树,那么概率还是 \(\frac{1}{dis(u,v)}\)。否则就会有两条路径,考虑容斥,减去多算的部分。设 \(d1\)、\(d2\) 分别为两条路径的点数,\(d3\) 为两条路径的并的点数,概率就是 \(\frac{1}{d1}+\frac{1}{d2}-\frac{1}{d3}\)。
代码:https://pastebin.ubuntu.com/p/NhQKfNGRxz/。
10.25
2022.10.25 考试 T1
10.26
「CF446C」DZY Loves Fibonacci Numbers(线段树 + Fibonacci 数列)
首先需要知道,\(fib_{n+m}=fib_{n+1}fib_m+fib_{n}fib_{m-1}\)。
那么就可以有:\(fib_i=fib_{i-1}fib_2+fib_{i-2}fib_1\)。
因为在区间内加入一个斐波那契数列,这个区间仍然满足斐波那契数列的关系,所以我们设这个数列为 \(\{a\}\),就有 \(a_i=fib_{i-1}a_{2}+fib_{i-2}a_{1}\)。
推导之后还可以得出 \(\sum a_i=fib_na_1+(fib_{n+1}-1)a_2\)。
那么对线段树上的区间维护 \(a_1,a_2,sum\) 就行了。
下传标记有点阴间。
代码:https://pastebin.ubuntu.com/p/wnFnBrP9Pg/。
「CF309E」Sheep(二分 + 贪心)
最大值最小,想到二分。
考虑从 \(1\sim n\) 依次确定每个位置填哪一个区间。维护每个区间能放的最远位置 \(mx_i\),记 \(cnt_j\) 表示 \(mx_i\le j\) 的 \(i\) 的个数。假设现在填到了位置 \(x\),那么无解当且仅当存在一个 \(cnt_j>j-x+1\)(这就是说这个区间不够填了)。否则找到最小的 \(j\) 满足 \(cnt_j=j-x+1\),将满足 \(mx<j\) 的右端点最小的区间填到这里。
贪心正确性见 https://www.cnblogs.com/PYWBKTDA/p/16458642.html。感性理解就是尽量减少相交的数量。
代码:https://pastebin.ubuntu.com/p/Q6JwFs3tmx/。
「POI2012」OKR-A Horrible Poem(字符串哈希 + 周期串的性质)
-
如果 \(len\) 是一个串的循环节,并且 \(k\times len\) 是串长的约数,那么 \(k\times len\) 也一定是这个串的循环节。
-
\(s_{l,r}\) 有一个长度为 \(len\) 的循环节的充要条件就是 \(s_{l,r-len}=s_{l+len,r}\)。
上面这两条性质很好证。
那么暴力怎么做?可以枚举区间长度的约数然后哈希判断。
考虑优化一下这个过程。记录 \(mn_x\) 表示 \(x\) 的最小质因子,那么每次我们把 \(len\leftarrow \frac{len}{mn_{len}}\),然后再判断是否仍然是循环节。

浙公网安备 33010602011771号