20230815题解


挂分比得分多。。。

挂了 145,得了 107。。。

T1 seq

思路分析

我们只考虑前 \(\lfloor \frac{n}{2} \rfloor\) 个数,这样一来, \(a,b,c\) 三个序列就是独立的。要求的是 \(a,b\) 两个序列的方案总数,并且满足 \(a_i \operatorname{xor} b_i = c_i,\sum{\operatorname{popcount}(c_i)=k}\)

如果直接计算 \(a, b\) ,显然 \(c\) 不好满足。但是异或是可逆的,对于一个给定的 \(a\),确定了 \(c\) 就相当于确定了 \(b\)。所以就是求 \(a,c\) 两个序列的总数,并要求 \(\sum{\operatorname{popcount}(c_i)}=k\)。这就很简单了,因为 \(a,c\) 互不干扰。记 \(w=\lfloor\frac{n}{2}\rfloor \times m\)\(a\) 一共有 \(w\) 个二进制位,所以有 \(2^w\) 种;\(c\) 可以在 \(w\) 个二进制位中选 \(k\) 个,所以有 \(\operatorname{C}_{w}^{k}\) 个方案。

然后是细节部分。挂分原因一是 \(w\) 很大,在处理组合数算阶乘的时候,必须要将 \(i\) 先取模再乘再取模;另外是当 \(n\) 是奇数的时候,中间那个位置选什么数都没关系,答案要再乘上 \(2^m\)

T2 connections

思路分析

挂分原因:题目说有向图我存了无向(警钟敲烂)。

而且作为 juruo,tarjan 算法也写挂了。

其实根本不用求什么环。由于题目已经保证了双连通,所以从 \(1\) 出发一定能访问所有点,而且由于所有点都能访问 \(1\),所以反图上 \(1\) 一定也能访问所有点。在正反图上从 \(1\) 出发各跑一次 dfs,正图上经过的边就能保证 \(1\) 到达所有点,反图上经过的边就能保证所有点都到达 \(1\),两者结合就能保证整张图双连通,而这最多也只需要 \(2n-2\) 条边,剩下不需要的随意输出 \(m-2n\) 个即可。

AC 代码

T3 subseq

思路分析

定义 \(f_i\) 表示从第 \(i\) 位开始匹配,至少需要多少个数才能超过 \(a\) 的匹配范围。有一个显然的贪心结论:\(f_i\) 应该从 \(i\) 之后最后一个首次出现的数转移过来。比如 \(2,1,3,1,2,3\)\(f_2\) 之后最后一个首次出现的数是第 \(5\) 位的 \(2\),所以 \(f_2 \leftarrow f_5+1\)

如何找到“最后一个首次出现的数”呢?用 \(nxt_i\) 表示第 \(i\) 位之后下一个和 \(i\) 取值相同的位置,再对 \(nxt\) 做一个前缀最大值,记为 \(mnxt_i\),则“最后一个首次出现的数”就是 \(mnxt_i\)。其实不需要用到什么队列优化。

这样,从后往前处理出 \(f_i \leftarrow f_{mnxt_i} + 1\),剩下的问题就是字典序。

首先 \(f_i=f_1\) 的部分是不用选取的,也是不能选取的。最优的匹配方案应该是从 \(f_i=f_1-1\) 的位置开始选取。 但是如果取到了一个值使得 \(f_i=f_1\) 的部分也有这个值,那么匹配时就会匹配到 \(f_i=f_1\) 的部分,而不会匹配到 \(f_i=f_1-1\) 的部分。所以我们要对满足 \(f_i=f_1\)\(a_i\) 打上标记,然后在 \(f_i=f_1-1\)\(a_i\) 中找到未被标记的最小 \(a_i\),这样第一个数就一定满足字典序最小。这时我们就可以发现这样的操作是可以递归的,如果第一位找到了 \(a_x\),想找出第二位,就先把 \(x\) 之后满足 \(f_i=f_x\)\(a_x\) 标记掉,再从 \(f_i=f_x-1\) 的位置中找到未被标记的最小 \(a_i\)。每次都如此,就能构造出答案。

AC 代码

T4 point

思路分析

对于每一个质数 \(p\),将是 \(p\) 的倍数的点建出一棵虚树,那么查询实际上就是询问虚树的子树直径。

如何做这个询问呢?既然是子树修改,我们不妨考虑 dfs 序转化成区间修改,用线段树维护。但是线段树会把原本在一个子树中的点分到左右两棵树中,这两部分不一定连续,我们怎么从不连续的部分合并出整棵树的直径呢?

有一个结论。两个点集并的直径的端点一定是原来两个点集直径的端点。

这样就可以维护了。

为了实现修改,我们更改一下线段树的建树方式,不是在虚树的 dfs 序上建线段树,而是在原树的 dfs 序上建线段树,但是动态开点,如果这个点不在虚树中,就不开这个点,相当于建一棵虚线段树。这样,单点修改就是多开一个点。

至于子树修改,如果将所有点新开,必然 MLE + TLE,但是我们发现,子树修改对应到 dfs 序上就是区间修改,而对应到线段树上,就是新开 \(O(\log n)\)完全二叉树。所以我们可以预先维护一棵所有点都加入的完全二叉树,修改时只要将指针指向完全二叉树中的对应部分,就相当于完成了开点。

时间复杂度 \(O(wq \log n)\)。(\(w\) 是质因数个数)。