ZR 2025 十一集训 Day 4
无论春夏 无论秋冬
无论多少岁月将我们分隔
CF1606E
思维难度:\(\color{#52C41A} 绿\) *1700
如何设计状态?
首先第一维一定是剩余 \(i\) 个人,这个是毫无疑问的。
然后看我们实际上关心的是什么。因为要算没有人胜利的方案数,而如果有人胜利,那么这个人一定是初始状态下所有人中血量最大的,且在后面的过程中也能一直保持最大。所以不妨把当前局面的最大血量丢到状态里,令 \(f_{i,j}\) 为剩余 \(i\) 个人,其中最大血量为 \(j\),最终没有胜者的方案数。
发现这是正确的,转移只需要考虑血量最大的人是否存活到下一轮即可。
天依宝宝可爱!
CF1716C
思维难度:\(\color{#F39C11} 橙\) *1000
大水题,注意到答案的形态就做完了。
但是被直接走直线的情况硬控 2h,甚至没有想到可以对于直接走直线直接暴力一遍(后来想到了才过了),糖糖糖糖糖。
天依宝宝可爱!
CF1716D
思维难度:\(\color{#F39C11} 橙\) *1100
诈骗题,注意到最多操作 \(O(\sqrt n)\) 次就做完了。
天依宝宝可爱!
洛谷 P3574
思维难度:\(\color{#FFC116} 黄\) *1300
比较简单,显然是树形 dp,重点在于确定子树顺序。不过写个子树个数多的 dp 方程就可以找到规律了。
例如三个子树:
前两项需要满足:
可以发现后面所有相邻两项,把包含的 \(sz_i + 2\) 都减掉之后和上式是一样的,直接按照这个排序即可。
天依宝宝可爱!
CF1748E
思维难度:\(\color{#F39C11} 橙\) *1100
注意到要求就是笛卡尔树相同就做完了。
分享一个给定 \(n \times m \le 10^6\) 但不具体规定 \(n,m\) 的开数组技巧,不需要用到 vector。
开一个结构体,里面存一个 \(10^6\) 的数组,然后重载
[]运算符即可,代码:struct { int v[M]; il int* operator[](int i){return v+i*(m+1);} il const int* operator[](int i)const{return v+i*(m+1);} } ;
天依宝宝可爱!
CF1830D
思维难度:\(\color{#52C41A} 绿\) *1900
挺 Ad-hoc 的一个题。反正我不会。
有一个显然的错误的贪心,就是黑白染色,但是很容易 hack,随便搞一个有几个点有很多个儿子的树就可以了。
那这时候该怎样是最优的呢?可以在黑白染色的基础上,舍弃一些点(存在一些相邻点颜色相同),尽量把那一堆儿子都染成 \(0\) 会更优。
显然可以考虑 dp。设计一个类似于「\(f_{u,0/1}\) 为子树 \(u\) 根结点染成颜色 \(0/1\) 的最大价值」的东西。
然后观察什么会影响答案,会以什么方式影响答案。发现答案其实就是全部路径的 \(\rm mex\) 均为 \(2\) 的答案 减去 实际上 \(\ne 2\) 的答案;且 \({\rm mex} \ne 2\) 只需要考虑每个同色连通块的贡献(因为跨连通块的路径上就一定会出现 \(2\) 种颜色了)。一个大小为 \(k\) 的同色连通块的贡献为 \(\frac {k(k+1) \times c} 2\),其中当颜色为 \(0\) 时,\(c=1\);颜色为 \(1\) 时,\(c=2\)。
那么就要设计 dp 使得所有同色连通块的贡献最小。
于是可以令 \(f_{u,i,0/1}\) 为子树 \(u\),\(u\) 所在的同色连通块大小为 \(i\)、颜色为 \(0/1\) 的最小贡献。背包地转移。
但是这样看起来是 \(O(n^2)\) 的呀!
先考虑优化转移,发现不能,毕竟背包这个东西除了 bitset 之外好像就没什么优化的地方了。
再考虑是否有大量没用的状态,发现如果连通块过大,一定不会很优秀。再回到初始的黑白染色——这个方案虽然不是最优但已经很优秀了,算一下发现会产生 \(< 2n\) 的贡献;那么一个大小为 \(i\) 的连通块贡献 \(\frac {i(i+1) \times c} 2\) 就需要 \(<2n\) 才可能有用,所以 \(i\) 只需要开到 \(O(\sqrt n)\) 级别就可以覆盖所有有用的状态了。
所以时间复杂度是 \(O(n \sqrt n)\) 的。
但是还有一个问题,就是空间复杂度,可以发现也是 \(O(n \sqrt n)\) 的,不能直接开。
所以考虑动态开,过程中只保留有用的部分。在 dfs 过程中,只保留在 \(u\) 到根结点的路径上的 dp 值,但是还是容易被卡到 \(O(n \sqrt n)\),构造一条链就可以。
继续观察,发现我们没必要在一开始就把 \(u\) 的空间开满,可以在遍历子树 \(v\) 后背包合并的时候,先释放 \(v\) 的空间,再把 \(u\) 的空间开到对应的大小(大小增加 \(sz_v\)),这样就相当于所用空间不变了,那么空间的开销就只在 dp 的初始化上(每个 \(u\) 都要初始化 \(f_{u,1,0/1} = 1/2\)),所以空间就是 \(O(n)\) 的了。
天依宝宝可爱!
CF1725J
思维难度:\(\color{#FFC116} 黄\) *1500
还是比较好想的。
显然题意可以转化为在一棵树上找两条链,分别有一倍的贡献,其他边有两倍的贡献。求最小贡献。
首先这两条链一定不会有边相交,因为如果有,那么考虑把相交部分(两个一倍贡献即两倍贡献)删掉,剩下的边恰好能构成另外两条不交的链,而此时相交部分还能有一条边不走(两倍贡献变成零被贡献),一定更优。
不过是可能有点相交的。
那么就分为两种情况:一是有一个点相交;二是不相交,并且此时可以选一个两条链之间的边不走(零倍贡献)。
差不多就做完了,换根 dp 搞一搞就可以了。有一个点相交就是一个点往外延伸 \(4\) 条链;不相交就枚举不走的边,在换根 \(u \to v\) 的时候就可以算掉不走边 \((u,v)\) 的答案。
天依宝宝可爱!
CF1798F
思维难度:\(\color{#52C41A} 绿\) *1700
神必结论题。真的会有人在不知道结论的情况下做出来吗。
Erdős-Ginzburg-Ziv 定理:对于任意 \(2n-1\) 个整数,总可以从中选出 \(n\) 个数使它们的和为 \(n\) 的倍数。
首先因为没给无解的样例,所以一定有解。
考虑构造,因为我们可以随意选择一个新数,所以只需要确定 \(m-1\)(此处 \(m\) 就是题目中的 \(k\))个 \(s_i\) 的选择,最后一个剩下什么要什么就可以。
因为使一个 \(s_i\) 合法至少要 \(2s_i - 1\) 个数,这有点多,所以贪心地想,把最大的 \(s_i\) 放到最后,看看是否可行。
于是将 \(s_i\) 排序,然后考虑极限情况,即 \(s_{m-1}\) 是否能找到 \(2s_{m-1} - 1\) 个数。发现是可以的。
Proof. 考虑反证,若 \(s_{m-1}\) 找不到那么多数,则:
\[\begin{aligned} 2s_{m-1} - 1 & > n - \sum _{i=1} ^{m-2} s_i \\ 2s_{m-1} - 1 & > n - (n+1 - s_m - s_{m-1}) \\ s_{m-1} & > s_m \end{aligned} \]显然不成立。QED.
所以就直接构造,对每个 \(s_i\) 找到合法选择后,后面的 \(s_i\) 就不能选已选过的数了,根据上面的分析,这样是一定可以找到一组解的。
具体地,对于每个 \(s_p\),考虑一个背包,令 \(f_{i,j,k}\) 为考虑前 \(i\) 个数,选了 \(j\) 个,当前已选的数之和 \(\bmod s_p = k\) 是否可行,注意之前的 \(s_x\) 已选的数不能选即可,转移是平凡的(\(c_i = 1/0\) 表示 \(a_i\) 是否之前已选):
然后是记录路径,可以直接再开个 \(g\) 表示转移方向,不过还有一个更好的方法。因为对于一个 \(f_{i,j,k}\),它只能从上面两条路径转移,即不选 \(a_i\) 和选 \(a_i\),所以只需要判断是否可以从选 \(a_i\) 转移过来,如果能就把 \(a_i\) 加入答案,否则跳过,就可以了。
时间复杂度 \(O(n^3)\),容易用 bitset 优化到 \(O(\frac {n^3} w)\)(若用 \(g\) 记录路径就不能 bitset 了)。
当然,根据大神的题解,如果懂了那个定理的证明,是有 \(O(n \log n)\) 做法的。
天依宝宝可爱!
CF1767E
思维难度:\(\color{#FFC116} 黄\) *1400
注意到 \(m\) 很小,考虑从这里下手。发现合法的染色方案的充要条件 是 不存在相邻两个位置都没有被染色,同时开头和结尾必须染色,于是可以将相邻位置的颜色连边,开头结尾颜色分别连自环,于是就转化成了求这张图上的带权最小点覆盖。
经典结论,最小点覆盖 = 点数 - 最大独立集。放到这个题上就是带了个权,就是带权最小点覆盖 = 点权和 - 最大权独立集。
最大权独立集有经典 \(O(2^{n/2})\) 做法,link。具体地,考虑一个状压 dp \(f_S\) 表示点集 \(S\) 的最大权独立集,则有转移:
其中 \(u\) 为 \(S\) 中任意一个点(哪个点都可以),\(w_u\) 为 \(u\) 的点权,\(e(u)\) 为图上和 \(u\) 相邻的点集。注意当 \(u\) 有自环时不能从后一半转移。
那么把 \(S\) 仅包含前 \(n/2\) 个点的 \(f_S\) 记忆化,复杂度是 \(O(2^{n/2})\) 的;\(S\) 包含后 \(n/2\) 个点则枚举后 \(n/2\) 个点是否被选,确定了后 \(n/2\) 个点的状态之后,根据一些 \(e(u)\) 的限制,就能对应到某个仅包含前 \(n/2\) 个点的状态了,复杂度也是 \(O(2^{n/2})\) 的。
一点很有用的剪枝,可用于乱搞,具体还是看上面给出的链接:\(u\) 取 \(S\) 中度数最大的点;\(S\) 不连通时分开考虑。
在实现上,如果不进行剪枝,每次选择的 \(u\) 就是 \(S\) 中编号最大的 \(u\) 即可,这样可以保证后 \(n/2\) 个点一定在 dfs 过程中先被考虑,最后就自然而然地剩下了前 \(n/2\) 个点。
il int dfs(int s) // 不进行剪枝
{
if(s<1<<20 && ~f[s]) return f[s]; // 此处默认 n=40
int u=63-__builtin_clzll(s);
int res=max(dfs(s^1ll<<u),ban[u]?-1ll: a[u]+dfs(s^1ll<<u^(e[u]&s))); // ban[u] 表示 u 有自环,也就是 u \in e[u]
return s<1<<20 ? (f[s]=res) : res;
}
天依宝宝可爱!
待补
CF1750F、CF1842G、CF1120F、CF1707D、CF1188D、CF924F、CF1208H、CF2039F2。

浙公网安备 33010602011771号