牛客练习赛130出题人题解
牛客练习赛130题解
赛后总结:
符合预期。
赛时来看, 题的样例有点弱,但是由于看到 wa 率大之后,发现的比较晚,考虑到时间因素,就没在赛时补充。~~
的树形 DP,可能由于
的影响,导致写的人比较少,同时可能也因为其需要点码量。
属于
的加强,通过
的启发,考察计数能力。
update:
D 题,题解给出了一些比较强的 case,便于大家赛后debug。
题,数据稍弱(感谢评论区提出),赛后增强了一些case数据。
A
- 若
,
次。
- 若
与
的二进制存在子集关系,
次。
- 否则
次。
B
先考虑无解的情况:
或者
。
否则必然存在一种合法的构造方式,这里给出一种比较简单的构造方法:
考虑前 步先设置成
,第
步设置成
。
令 ,若
说明骑士血量是多了,需要调整前
步的大小。
由于要保证骑士前 步不能死,所以考虑从第
步倒序调整,对于每个
还能减少:
,然后更新
。
C
推一下式子,很容易得到:
考虑修改怎么维护这个值,由于是单点修改,我们可以考虑本次修改对答案带来的变化即可。
记 为目前的答案,容易推出:
- 若令
,则
。
- 若令
,则
。
故我们只需要两个树状数组动态维护 的前缀和即可,时间复杂度
。
D
首先考虑最好的序列的特征,要让未出现的最小正整数最大,显然我们需要尽可能的让 ,尽可能出现在序列中。
又观察到 ,那么未出现的最小正整数最大就是
。
令 ,此时
满足
,且
,那么我们的答案至少是
。
那么我们答案能不能更大呢?
显然是可以的,考虑序列中的另一个数 ,是有可能存在
的。
不妨设 ,那么我们可以枚举其序列最终可能的
。
现在问题转化为如何判断最好的序列的 可以为
。
结合算术基本定理:
若
,
,
那么
我们可以考虑 相比
,多出了哪些
。
结合 与
,容易发现
。
那么将 进行质因数分解,其最多只有
个不同的质因子,那么令
(
),
表示
相较于
多乘的质数次幂部分。
由于我们只有 个数,有
个数已经确定了填了
,那么还剩下
个数,我们需要选择一下数放进去,可以使得其多出
这部分。
我们要保证往序列里面加入一些数,使得其多出 这部分,同时要满足序列的最大值
。
考虑到 的限制,可能会导致放了
之后,剩余的位置不够,我们可能需要把一些
合并,由于
,直接暴力检查所有可能即可。
这里给出两种做法:
第一种:直接大力分类讨论
,那么我们可以直接往序列里面加入:
这
个数。
- 否则说明我们位置不够,否则我们需要合并一些质数次幂,即将他们乘到一起,按
进行分类讨论即可。
- 如果
,那么说明只剩下一个位置,只能把所有
相乘,然后直接判断其是否
。
- 特别的如果
,如果只需要合并一次,可以考虑选择最小的两个
乘起来,然后判断。
- 如果
第二种:可以直接 DFS 或者全排列枚举合并的可能,或者写个简单的状压 DP。
第二种做法比较不容易出错,第一种做法的分讨实现起来需要注意细节。
一些比较强的 case:
input:
8
32 100000000000000 72201776446300
32 1145 69872686884000
31 555 72201776446300
32 78956421 69872686884000
31 98788985 72201776446309
7 100 372
23 16568 1629547920
17 1145 8648640
output:
69872686884000
67543597321200
53569059944400
69872686884000
69872686884000
360
1629547920
7927920
E
出题人做法:
令 表示以
为根的子树,用了
次操作,其中有
个异色的儿子,
表示
的颜色。
不难发现其转移就是树上背包的合并,每次枚举 ,其中分别表示:在合并了前几个儿子,当前以
为根的用了 次操作,以
为根用了
次,
的异色儿子个个数,
的异色儿子个数,直接暴力转移是
级别的。
考虑经典的子树 size 的优化:
for (int i = 1; i <= std::min(sz[u], k); i++)
for (int j = 1; j <= std::min(sz[v], k) && i + j <= k; j++)
这里优化完,复杂度可以做到 ,考虑继续优化,观察树上背包合并的转移式子。
容易发现 dp[v][j][sv][!nc] + sv * (sv - 1) / 2 + su * (su - 1) / 2
像这样的式子可以提前预处理出来最大值,因为都是用的儿子 的
去算的。
可以在转移之前,先做一些预处理:
static int maxv1[N][2], maxv2[N][2];
for (int i = 0; i <= std::min(k, sz[v]); i++) {
for (int j = 0; j < 2; j++) {
maxv1[i][j] = maxv2[i][j] = -inf;
}
for (int sv = 0; sv <= (int)g[v].size(); sv++) {
for (int nc = 0; nc < 2; nc++) {
maxv1[i][nc] = std::max(maxv1[i][nc], dp[v][i][sv][nc]);
maxv2[i][nc] = std::max(maxv2[i][nc], dp[v][i][sv][nc] + sv * (sv - 1) / 2);
}
}
}
这样转移最内层循环就可以被优化掉,最终时间复杂度 。
在内测验题中,其实也有其他的状态设计,例如可以不记录异色儿子个数,而是在转移的时候去处理。
F
经过 Easy 版本的启发,现在考虑如何进行计数。
我们令 ,其中
为未出现的最小正整数最大值。
我们需要枚举最后序列的 为
,并且计算其方案数,最后累加即可。
至此,在保证未出现的最小正整数最大的情况下,我们需要解决两个问题:
为
的方案数是多少。
- 若已知可以
选择的数量为
,且需要保证
至少出现一次的方案数是多少。
容易发现问题 是一个经典的容斥问题,结合容斥原理和组合数学可以求解。
对于问题 ,我们直接求
等于
的不好求,我们考虑定义:
表示
为
的约数的方案数有多少?
记 为
所有满足
的因子个数,不难发现直接就变成了问题
。
定义 表示
的方案数,由简单的容斥可以得到:
。
考虑每部分计算的时间复杂度,对于 的这些因子我们可以提前预处理出来,这块复杂度并不高。
对于每次枚举 ,
的计算可以从预处理的 vector 二分出来,这里
复杂度就可以解决。
考虑枚举 和问题
的计算,复杂度大约为
,假设预处大约用时为
,最终复杂度大约为
。
这里复杂度瓶颈在于每次计算方案,需要枚举 ,然后里面要做组合数学,容斥,还需要用到快速幂。
考虑到时限,最终给定 s,只要预处理做好了,正常的实现可以在
s 内跑过。
实测,其实可以当 太大,做快速幂的时候,可以考虑降幂,可以优化快速幂。