三道 Petrozavodsk 和 Open Cup
Different Summands Counng
反套路题。介绍一下 $O(m^3\log n)$ 的卡常做法和 $O(m^3)$ 的标算。
我们考虑对于一个数,算出它恰好没有出现过的方案数,然后用总方案减去这个就是出现过的方案数量。前面这个可以方便地容斥成钦定,而如果是钦定且这题是有序拆分就可以用插板来安排剩下的数。
这个东西看起来比较难算。我们考虑第一层直接枚举,怎么计算后面这个东西。
我们倍增计算出所有的 $\binom{n-ak-1}{j}$ 。具体来说,我们先计算出
然后考虑怎么去计算前半部分。
由于一个组合恒等式,有
所以可以 $O(m^2)$ 转移出所有的 $F(j)$ 。复杂度 $O(m^3 \log n)$ ,转移时可以 NTT 做到 $O(m^2\log m\log n)$,写着很难受而且很不优秀。
标算做法非常反套路。具体来说,我们把 $a$ 看作 $x$ ,于是会发现 $\binom{n-kx-1}{m-k-1}$ 这个玩意是个 $O(m)$ 次项的多项式。所以说我们直接把多项式暴力算出来(这部分可以暴力或者 NTT),然后去求一个自然数幂和来算这个多项式的 $\sum F(a)$ 。这样复杂度仅仅是 $O(m^3)$ 或者 $O(m^2\log m)$ ,非常优秀。
第一种做法是当 $n$ 很大的时候比较容易想到的套路做法,也就是去分治,第二种是一种优秀但是比较特殊的做法。
1 |
|
B Process with Constant Sum
我可能比较擅长这种题。
我们来手玩一下样例以及手捏的数据,会发现最后一段区间操作完后一定会成为一段 $0$ 再拼一段 $1$ 再拼一个 $x$ 。具体来说,我们可以这样操作:首先对每个数 $\bmod 2$ ,把所有 $\ge 2$ 的数用操作把 $x - x\bmod 2$ 送到结尾。然后找到倒数第二段 $1$ ,如果它和最后一段之间隔了奇数个 $0$ ,会发现你可以使用操作把最后两段互相削除掉。如果它和最后一段之间隔了偶数个$0$ ,会发现可以使用操作把这两段拼起来(这两部分需要仔细想想,有点细节)。而且会发现先拼 / 削任意两段是不会有区别的,也就是操作顺序其实不影响。
然后就是一些套路操作了,因为顺序不影响,我们把一个区间会得到的最终状态放到线段树上,在一个区间维护前面 $0$ 的长度,中间 $1$ 的长度,最后 $x$ 是多少。合并就是上面说的,需要想一想,细节可以参考代码。修改就是正常的线段树单点修改。
复杂度 $O(n\log n)$ 。
1 |
|
C Interest
神仙题
会发现这是以前 DLS round 的第三题,AUOJ 459。
我们考虑 $O(n^2)$ 暴力怎么做。可以看出选出两条边不相交路径可以直接看成一个网络流问题,也就是求出 $1$ 到点 $u$ 的大小为 $2$ 的最小费用流,且每条边容量为 $1$ ,然后就可以类似环游那个题真正的跑 $2$ 次单路增广。
首先第一次流其实可以非常简单的得出,也就是当我们建出最短路树后,第一次流一定是从 $1$ 在最短路树上流到 $u$ ,可以一次 dijkstra
实现。我们考虑能否通过一次 dijkstra
做完第二次流。但是在流了一次并反向后会得到负权边,所以我们可以对最开始的图做一次 primal dual
,也就是设到每个点最短路为 $d_u$ ,那么把 $u \to v$ 边权为 $w$ 的边的边权设置为 $w’ = w - d_v + d_u$ 。不难发现这样操作后最短路树上的边一定全部变成了 $0$ ,并且对于一条 $1 \to k$ 的路径,实际长度一定正好是 $d_k + dis$ 。并且此时,如果我们对 $1\to u$ 的路径全部反向,所有边的长度仍然是 $\ge 0$ 的!
于是我们考虑做一个 dijkstra
的过程,也就是我们设 $d_u’$ 为在 $1 \to u$ 的边被反向时,$1 \to u$ 的最短路。然后我们仍然考虑类 dijkstra
的转移,也就是每次拿出堆顶来更新其他点。考虑当前堆顶为 $u$ ,那么转移有两种。首先,这个点自己连出去的边肯定可以转移,对于一条非树边 $(u,v,w)$ ,一定可以用 $d_u + w$ 来更新 $d_v$ 。
然后考虑还可以用 $d_u$ 更新什么,会发现,其实 $1 \to u$ 的边被反向后等价于当前的根换成了 $u$ ,我们考虑对于一对其他的边 $(a,b,w)$ ,我们可以用 $d_u + w$ 来更新 $b$ 当且仅当 $a,b$ 不在 $u$ 的同一棵子树。因为如果把 $1 \to b$ 的路径反向其实等价于在这个图上把 $u \to b$ 的路径反向,然后如果 $a,b$ 不在同一个子树,那么把 $1 \to b$ 反向后仍然可以从 $u$ 以 $0$ 的代价走到 $a$ ,否则一定走不到。所以可以这样更新。
但是直接这样更新需要枚举 $u$ 的所有子树,这是不优秀的。但是考虑 $u$ 的某个子树中如果存在某个点 $v$ ,而 $d_v < d_u$ ,即 $v$ 已经作为堆顶过了,那么 $v$ 的子树一定是不用更新的。因为 $v$ 子树内部一定不能更新, $v$ 子树向外已经被 $v$ 更新过了。所以我们更新完一个点 $u$ 后其实可以直接把 $u$ 删去,也就是做一个类似点分的过程。
但是这种启发式点分也很容易被卡到 $O(n^2)$ 。我们考虑在更新一个点 $u$ 时,其实可以不用枚举这个点的最大的子树。我们可以对每个点存下这个点的所有出边即入边,然后枚举所有非最大的子树以及它们的出入边即可。我们可以证明这样每个点被枚举的次数是不超过 $O(\log n)$ 的,因为每次它作为轻子树出现,就说明这次点分之前它所在子树大小会翻倍。
然后还需要考虑最后一个问题,怎么判断哪个子树是最大的。考虑维护一个当前最大的子树 $mx$ 。考虑用 $O(\min (sz_u,sz_v))$ 的复杂度比大小。具体来说,可以倍增一个上界 $lim$ ,然后在两个子树分别 dfs
$lim$ 次,如果其中某一个没 dfs
到这么多次即可直接比较,否则倍增 $lim$ 。可以发现这样实际上只会用 $O(\min(sz))$ 次。所以,每次枚举一个子树后如果它比当前子树大,那么我们去把当前最大子树给 dfs
一次枚举所有出入边,然后把最大的子树修改成当前子树,否则对当前子树做即可。
前面说明了,这样的复杂度是 $O(m\log n)$ 的。
1 |
|