牛客游记合集

· · 生活·游记

2024 牛客暑期多校训练营

整个比赛我是单打,脑子根本不够用,不要嘲讽我。

部分本文章同步发表于牛客多校比赛题解区。

为了宣传高端的洛谷文章区渲染方式我的洛谷博客,我会在每个牛客博客后方这段话:

# [强推我的洛谷博客(或者说文章区)](https://www.luogu.com.cn/user/377873#article)

# [如果渲染格式有问题,去我的洛谷博客](https://www.luogu.com.cn/article/zbyqzodf)

1

把签到题切了之后看 A,A 经过思考,秒了,开始肝 I,I 用的记搜,边界条件太多,我被秒了,很失败。

赛后肝完了 I,把 B 和 D 研究后做出来了。

A

最后一位是关键,我们可以证明:序列合法与序列中奇数元素的与和是 1 等价。

枚举奇数个数,确保每个前 m-1 位在这些奇数中都有一个零即可。

B

同样,最后一位是关键。

定义合法子序列为与和为 1 的子序列。

至少有两个合法子序列的方案数等于至少有一个合法子序列的方案数减恰好有一个合法子序列的方案数。

“至少有一个合法子序列的方案数”在 A 题中被我们秒了,我们现在需要求“减恰好有一个合法子序列的方案数”。

还是枚举奇数个数,可以发现,题目中要求的两个合法子序列肯定有一个是由所有奇数组成的子序列。我们定义在奇数中,前 m-1 位恰好只有一个数在这一位是 0 的位为特殊位,“减恰好有一个合法子序列的方案”即为这些奇数中每一个数都被分到一个特殊位(这样删掉任何一个奇数都无法找出合法子序列)。

在枚举奇数个数的循环中,我们枚举特殊位数量(从奇数个数到 m-1),之后计算“减恰好有一个合法子序列的方案数”,这题就做完了,记得非特殊位并不是乱来的,他们在奇数中至少有两个 0(这个的计算方法还是整体减部分,所有的方案数减恰好没有 0 和只有一个 0 的方案数)。

关于将特殊位分给奇数的方案数:

设特殊位有 j 个,奇数有 i 个,f_{i,j} 为方案数,则递推式为 f_{i,j}=i\times(f_{i,j-1}+f{i-1,j-1}) ,分为放在某一类和用一个元素替换掉之前的一个类并把那一个类放到当前的新类两种情况。

C

打卡题。

\sum_{i=1}^n\sum_{j=i}^na_j=\sum_{i=1}^na_i\times i

没了。

D

打卡题进阶版。

在评价题目中看见很多人用了“结论题”这个标签,自习想想也对,赛后讲解没懂,同学给我讲的时候我很懵,突然会了后感觉这题没这么难,我现在在写题解,除了直接写做法好像写不出来什么思考过程。

把题目要求的变形一下,定义数组 s 为前缀和,那么题目会变成这样(注意从 0 开始):

\bigoplus_{i=0}^ns_n-s_i

我们可以发现,这是一个关于 s_n 的函数。

观察到模数是 2 的整数次幂,我们直接建立 21 个树状数组大小分别为 2,4,8,16\dots2^{21},第 k 个树状数组维护 s_i 的后 k 位组成的桶子。

对于答案中的每一位,我们可以在对应的桶子中计算与 s_n 的差恰好在当前为是 1 的数的个数即可。

具体一点:

ans=\sum_{i=0}^{20}\sum_{j=s_n\,\text{mod}\,2^{i+1}+1}^{s_n\,\text{mod}\,2^{i+1}+2^i}box_{i,j}\,\text{mod}\,2^{i+1}

代码不是特别难写(相比 I)。

E(不会)

F(不会)

G(不会)

H

签到题。

模拟即可,让所有双票选手躲着幸运观众。

I

一个好写的方法:记搜,把每一个格子拆成 8 个点,分为上下左右进来和出去的。

连边,跑 dfs,做完了。

这题难点在排重。

自己造的一个 hack 大全:

3 4
----
\-/-
||\|

48 个可能的询问,好多都是 hack,自己去试试吧(别让我教你怎么和 std 对拍)。

J(不会)

差不多就是两个坐标分开考虑,数据结构优化。

K(不会)

2

在前一天问了一下比我小一级的三个同校大佬的队,他们第一场切了 5 个题,%%%(我竟然被单调队列了?)。

这次我随机开题,开到了 H,试图强首杀,但是想错了,调了半天,吃饱了罚时才过,把 E、H 秒了,之后思考 C,不就是简单 DP 吗?秒了,开始做 B,感觉像高科技,但是好像根号分治可以卡过去(下面的题解我讲的是根号分治做法),就开始调,在吃了 3 个罚时后肝了出来,之后思考了一个小时 I,发现就是个会加边的 DAG(你叫它 DP 也行),之后 15 分钟秒了,开始摆烂(当时已经封榜了,看着那三个同校小孩哥不断的提交我一点思路都没有的 G,很慌,怕再次被单调队列,但还好他们没调处来。)

A(不会)

B

根号分治做法

我太菜了,只会库鲁斯卡尔算最小生成树,库鲁斯卡尔需要对边排序,通过人类的智慧,我们发现每个询问用到的边应该不会太多,我们分两种情况。

也不能叫根号分治吧,定义 K 为分治边界。

点数大于分治边界的查询在整个边集则在全边集中找有用边跑库鲁斯卡尔,否则两两枚举找有用边。

经过计算 K = 120 时理论时间复杂度最优(复杂度一个是 K\log K,一个是 \frac{100000}K),但因为奇妙的常数原因好像并不是这样的。

我们找到了这一次有用边组成的边集后进行排序,排序后无脑跑库鲁斯卡尔就行,我的并查集甚至只用了一个路径压缩优化。(是的,我知道你会惊讶地说:“这都能过?!”)

C

定义 dp[i][j] 为 走到 (i,j),之后要往右走,最大的价值。

转移方程:

dp[1][i] = max(max(val[1][i], val[1][i] + val[2][i]), max(dp[1][i - 1] + val[1][i], dp[2][i - 1] + val[1][i] + val[2][i]));
dp[2][i] = max(max(val[2][i], val[1][i] + val[2][i]), max(dp[2][i - 1] + val[2][i], dp[1][i - 1] + val[2][i] + val[1][i]));

D(不会)

E

打卡题。

如果输入是二的正整数次幂,可以证明无解。

否则输出 x-\text{lowbit}(x) 就过了,可以证明这是对的。

F(不会)

G

赛时的想法:

首先可以把每一个数扔到完全平方因数后的数的质因数分解抽象成一个二进制数(1000 以内的质数有 168 个),之后算异或和为 0 的子序列数量(这个好像很典,但我太菜了,不会)

题解:

首先可以把每一个数扔掉完全平方因数(记录一下价值)后的数的质因数分解分为“小质数”和“大质数”,以 \sqrt{1000} 为边界,把“小质数”抽象成一个二进制数。

我们考虑 DP(你可以把它叫 01 背包,但物品重量是按照异或来相加的)。

先处理没有大质数因数的数,之后对于每一个大质数,进行处理,每一个大质数可以被抽象为分组背包,每一组中要去恰好偶数个物品。

具体贡献计算你可以自己思考或者看代码,记得要随时取模。

H

先用前缀和预处理从第一个操作做到当前操作后到达的位置,倒序考虑,维护记录相对位置的桶子即可(桶子里面记录有关最近一个到达那个相对位置的操作序号之类的信息)。

具体方法这题看代码更好理解:

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, a, b, ans;
char c;
pair<int, int> m[1000005];
map<pair<int, int>, int> t;
signed main()
{
    cin >> n >> a >> b;
    if (a == 0 && b == 0) {
        cout << n * (n + 1) / 2;
        return 0;
    }
    for (int i = 1; i <= n; i++) {
        cin >> c;
        if (c == 'A') m[i] = {m[i - 1].first - 1, m[i - 1].second + 0};
        if (c == 'S') m[i] = {m[i - 1].first + 0, m[i - 1].second - 1};
        if (c == 'W') m[i] = {m[i - 1].first + 0, m[i - 1].second + 1};
        if (c == 'D') m[i] = {m[i - 1].first + 1, m[i - 1].second + 0};
    }
    for (int i = n; i > 0; i--) {
        t[{m[i].first - a, m[i].second - b}] = n - i + 1;
        ans += t[{m[i - 1].first, m[i - 1].second}];
    }
    cout << ans;
    return 0;
}

I

为了描述方便,我用图论的说法讲了。

我们先把 n 增加一,把原数组用两个 0 套起来,这样我们就必须把数组用完了。

节点对应卡片,节点编号对应卡片序号。

第一步,我们先对于任何一个节点,向编号下一个节点连一个边权为“随意”的边。

我们从两张卡片间隔小到大的卡片一次考虑,每一次考虑,我们计算从这种卡片的前面的一张所对应的节点和这种卡片后面的一张的紧接着的后面的卡片对应的点的最长路(其中,“随意”边权的边在这一轮计算时被当做边权为卡片上面的数字的边),计算完最长路后,我们从这种卡片的前面的一张所对应的节点到这种卡片后面的一张的紧接着的后面的卡片对应的点连接一个边权为最长路的边。

### J(不会) ## 3 考炸了,又被学弟吊打(那队学弟只有俩人)。 被单调队列了。 ### A(不会) ### B 每次最小移动 $\gcd_{i=1}^nh_i$,那么输出 $\min(D\mod\gcd_{i=1}^nh_i,\;\gcd_{i=1}^nh_i-D\mod\gcd_{i=1}^nh_i)$ 即可满分。 证明我不会,不在牛客发博客了。。。 ### C(不会) ### D(不会) ### E(不会) ### F(不会) ### G(不会) ### H(不会) ### I(不会) ### J 倍增找到从一个位置开始,会从哪里结束,它的父亲就是结束位置的后面一个位置,建立基环森林,在基环森林的每一个节点倍增找到答案。 ### K(不会) ### L 可以证明非边界的地方一定至少有一个 $8$,除了它,全放雷就可以。 ## 4 终于校内 rk1 了,但最后一题是距离结束 5 分钟调完的。 ### A 带权并查集,在保证并查集的根节点为原图根节点时对于每个并查集根节点维护它到子树内最远的节点的距离,并对于每个点,维护它到它并查集上父亲的距离,记得在路径压缩上维护这个。 ### B(不会) ### C 找环,对于大小大于 $2$ 的环,通过一次操作使得它的大小减 $3$,如果大小小于 $2$ 就退出。 会剩下一坨大小为 $2$ 的环,把这些环两两匹配进行操作即可。 ### D(不会) ### E(不会) ### F 枚举 $dis(u,v)$,根据人类的智慧,这个值在 $\sqrt x$ 附近,之后,贪心分讨旁逸斜出的节点即可。 ### G 在两端河分别做将军饮马即可。 ### H 不会证,但逻辑是排序后求两两差之间的最大公约数。 ### I 一段合法区间肯定唯一与一对朋友映射,朋友对 $(l,r)$ 合法当且仅当他们挨着或 $(l+1,r)$ 与 $(l,r-1)$ 同时合法。 从距离小到大枚举朋友对,用一个 `set` 记录合法的朋友对数,最后输出 `set.size() + n` 即可满分。 ### J 维护一个大小为 $k+1$ 的数组,维护当 $m\in[i,k]$ 以当前节点为右端点时连续 $1$ 的 $m$ 次方的期望。 计算新答案的时候无脑转移即可(需要预处理组合数)。 ### K(不会) ### L(不会) ## 5 被吊打了,没想出来 H ### A(不会) ### B 分讨。 ### C(不会) ### D 比对面大的肯定自己可以赢,比对面小的肯定赢不了,和对面一样的就争谁剩的多了。 ### E(不会) ### F(不会) ### G(不会) ### H dfs + 剪枝。 ### I(不会) ### J(不会) ### K(不会) ### L 暴力调换,前面比后面小就换。 ### M(不会) ## 6 终于又校内 rk1 了,没有被单调队列。 秒了 H B,之后做了 A F,最后半小时开始肝 D,竟然肝出来了。 ### A 转化题意后发现 Bob 随时可以叫停。 跑最大最小原理即可,边界和特判比较多。 ### B 瞎试了一个式子,就过了 `((n == min(m, n - m) * 2) ? n : (n * min(m, n - m)))`。 ### C(不会) ### D 只看轮边,跑 E-DCC,之后缩点跑生成树。 ### E(不会) ### F 分讨,把每棵树的根定为任何直径的任何端点,按层输出所有树,特判菊花森林和菊花图。 ### G(不会) ### H 模拟即可。 ### I(不会) ### J(不会) ### K(不会) ## 7 \_\_Dice\_\_ 大佬和 WangBX 大佬开始带我。 秒了签到,D 有原题,我们都做过,Dice 魔改了一下他之前的代码就秒了。 ## 8 两位大佬继续带我。 秒了简单的那个签到,放弃一个签到,去肝 E,被卡常,Dice 用了一个高科技秒了,之后他又秒了另一个签到。 上了点分。