atcoder杂题

dp + 计数

不得不说 atcoder 的题质量确实高,代码短且思维含量高(这也导致在洛谷上 at 的题普遍难度虚低)。

这里讲一些简单题。

Not High Element

首先能观察出来的一个策略是每次都把当前能操作的最小值放到前面去。

如果当前序列开头为 \(x\),那么一定是花费 \(2 ^ {x - 1} - 1\) 的代价(数学归纳法简单好证)将开头变为 \(1,2,⋯,x\);接下来研究紧接着的没续上的前缀最大值是谁,花费 \(1\) 次把它换到开头,一直重复。发现设序列中所有满足不是前缀最大值的数 \(x\) 的集合为 \(S\),答案就是 \(\sum_{x \in S} 2 ^ {x - 1}\) 。拆贡献即可。如果前缀信息能确定 \(x\) 是否满足条件,贡献就是 \((n−k)!\),否则根据概率计算,贡献显然是 \((n−k)! \frac{n - x + 1}{n - x}\)

code

Reversi 3

严肃被绿题曹飞。

转换抹多的题。我们发现原序列 \(a_1\)\(a_n\) 不会变,那么若 \(a_1 \ne b_1\)\(a_n \ne b_n\) 直接无解。考虑讲两个序列变成前缀亦或差分,并将奇数项取反(这是常用 trick ,用于 01 串翻转之类的),注意到原问题转换为了移动 \(a\) 序列中的 \(1\) 使其变成 \(b\) 序列(手模一下就知道为什么了),当两序列中的 \(1\) 数量不同直接无解,否则从记录每个 \(a\)\(1\) 的位置于 \(vec1\) 数组,\(b\) 中每个 \(1\) 的位置于 \(vec2\),答案即为 \(\sum |vec1_i - vec2_i|\),就是移动 \(1\) 所需的步数。

答案需要开 long long

code

Distinct Sum Grid Path

我们考虑将路径编号分为 \([0,\binom{2n - 2}{n - 1})\),且向右优先于向下。

对于一个点 \((i, j)\),我们记其到终点的路径条数为 \(f(i, j)\),显然有插板法 \(f(i, j) = \binom{n - i + n - j}{n - i}\)。设 \(pre_{i, j}\) 表示从 \((1, 1)\)\((i, j)\) 得分最小的一条路径的得分,在记一个 \([l, r)\) 表示经过 \((i, j)\)\((n, n)\) 的路径的得分区间,显然有 \(r = l + f(i, j)\)

我们列出表格:

\[\begin{aligned} &(i, j) \quad \quad \quad (i, j + 1) \\ &(i + 1, j) \end{aligned} \]

显然只考虑从 \((i, j)\) 转移到 \((i, j + 1)\)\((i + 1, j)\) 的区间为:

\[\begin{aligned} &[l, r) \quad \quad \quad [l, l + f(i, j + 1)) \\ &[r - f(i + 1, j), r) \end{aligned} \]

对于 \((i, j + 1)\),显然有:

\[l = mn_{i, j} + a_{i, j + 1} \]

对于 \((i + 1, j)\),有:

\[r - f(i + 1, j) = mn_{i, j} + a_{i + 1, j} \]

联立,得:

\[\begin{aligned} a_{i + 1, j} &= r - l - f(i + 1, j) + a_{i, j + 1} \\ &=f(i, j) - f(i + 1, j) + a_{i, j + 1} \end{aligned} \]

递推求解即可。

code

ABS Ball

好题。

我们钦定 \(\forall a_i \ge b_i\),然后答案就变成了原题答案的 \(\frac{1}{2 ^ m}\)。我们枚举 \(\sum a_i\)\(\sum b_i\),这两个和肯定为 \(n\) 所以知道一个就知道另一个。我们设 \(x = \sum a_i - \sum b_i\)\(x_i = a_i - b_i\),那么这种情况下答案显然就为 \(x_1 + x_2 + \dots + x_m = x\) 限制下的 \(\prod_i x_i\)

这样直接算是不好算的,我们考虑转化。这里有个常用的 trick,可以将乘积转化为方案数\(x_i\) 可以转换为在 \(x_i\) 个球中选一个球的方案数。我们知道 \(x_1 + x_2 + \dots + x_m = x\) 是个插板法,将后面的转化结合,贡献就转化为将一个长为 \(x\) 的段分为 \(m\) 段并且每一段选一个的方案数。注意到可以插 \(2m - 1\) 个板,第奇数个板就表示选的球,第偶数个板表示分隔区间的板,这样子的贡献就为:

\[\binom{(x - m) + m + m - 1}{m + m - 1} = \binom{x + m - 1}{2m - 1} \]

注意奇数个板也表示球且奇数个板有 \(m\) 个所以要将球数减去 \(m\)

对于每个这样的贡献,首先可以将 \(\sum b_i\) 个蓝球随便分有 \(\binom{\sum b_i + m - 1}{m - 1}\) 个方案数,然后再乘上 \(2 ^ m\),总答案为:

\[\sum_{a \ge b 且 a + b = n} 2 ^ m \binom{b + m - 1}{m - 1} \binom{(a - b) + m - 1}{2m - 1} \]

其中的 \(a = \sum a_i\)\(b = \sum b_i\)

code

Catch All Apples

被一大坨转换创飞了。

我们注意到如果机器人要从先捡苹果 1 再捡苹果 2,那么两者须满足的关系式为:

\[|X_1 - X_2| \le T_2 - T_1 \]

拆开绝对值,得:

\[\begin{cases} T_1 - X_1 \le T_2 - X_2 \\ T_1 + X_1 \le T_2 + X_2 \end{cases} \]

我们可以建出新图,设 \(x_i = T_i - X_i\)\(y_i = T_i + X_i\)。那么一个机器人若能先捡 \(i\) 再捡 \(j\),两点就需满足 \(x_i \le x_j\)\(y_i \le y_j\) 的偏序关系。我们发现这是一个最小链覆盖数量问题,可以转换为最长下降子序列,dp 即可。

关于最小覆盖链问题

链:一组点,互相都能到达(即:\(u\) 增加时,\(v\) 也增加)。

反链:一组点,其中任意两点都无法通过机器人到达(即:\(u\) 增加时,\(v\) 反而减小)。

覆盖链的数量应该等于最长反链,感性理解应该不难。最长反链显然是个最长下降子序列。

code

Paint Tree 2

注意到以 \(x\) 为根的子树情况一共有三种,\(x\)\(x\) 子树有 2 个链一起被染,和子树中一条链一起被染,\(x\) 没有被染。我们假设 \(pos_{x, j, t}\) 表示 \(x\) 花费 \(j\) 次染色与子树中 \(t\) 条链一起被染的答案。为了方便表示,设 \(f_{x, j}\) 表示共和子树中 2 条链一起染花费 \(j\)\(dp_{x, j}\) 表示和子树中一条且花费 \(j\)\(g_{x, j}\) 表示 \(x\) 不染色花费 \(j\)。对于每一个儿子 \(to\),有:

\[pos_{x, 1 \le j \le k + 1, t} = max_{jj}\{pos_{x, j - jj, t} + \max(f_{to, jj}, g_{to, jj})\} \]

对于 \(t > 0\),有:

\[pos_{x, 1 \le j \le k + 1, t \ge 1} = max_{jj}\{pos_{x, j - jj, t - 1} + dp_{to, jj}\} \]

根据定义,显然有:

\[g_{x, j} = pos_{x, j, 0} \quad dp_{x, j} = pos_{x, j, 1} + a_x \quad f_{x, j} = max(pos_{x, j, 1}, pos_{x, j + 1, 2}) + a_x \]

答案就是最大的 \(g\)\(f\)

code

Yet Another Grid Task

注意到每一列如果合法,黑格子若有 \(i\) 个,显然黑格子应分布在 \([n - i + 1, n]\),且下一列的黑格子数至少为 \(i - 1\) 个。那么我们可以设 \(dp_{i, j}\) 表示第 \(i\) 列有 \(j\) 个黑格子,显然有:

\[dp_{i, j} = \sum_{j - 1 \le k \le n} dp_{i + 1, k} \]

最终答案为 \(\sum_i dp_{1, i}\),加个前缀和优化就做完了。注意细节。

code

Counting Shortest Paths

这道题主要难点就是完全图的删边最短路,计数就是个容斥。只是难想,但看到代码一下就懂了。

将没有到过的点建一个 set 取名为 \(s\)。每次 bfs 到一个 \(x\) 点,就新建一个 \(t = s\),并将 \(t\) 中和 \(x\) 有删边的点删去,然后再把 \(t\) 中的点设为 \(to\),有 \(dis_{to} = dis_x + 1\),将 \(to\)\(s\) 中删去并加入队列。

code

Electric Circuit

期望转计数。\(n \le 17\) 考虑状压。\(g_i\) 表示 \(i\) 集合下独立连边的方案数(即没有对 \(i\) 外的点连边),显然 \(i\) 内的红蓝端口数应相等(把两者一个看成出边一个看成入边就知道为什么了),设总红端口数为 \(r\),蓝端口数为 \(b\),显然有 \(g_i = [r = b] \times r!\)。再设 \(f_i\) 表示 \(i\) 集合中恰好有一个联通分量的方案数,有:

\[f_i = g_i - \sum_{j \in i, \operatorname{lowbit(j)} = \operatorname{lowbit(i)}} f_j \times g_{i \oplus j} \]

\(\operatorname{lowbit(j)} = \operatorname{lowbit(i)}\) 的限制是防止容斥掉了重复的部分。然后最后设 \(dp_i\)\(i\) 的总和,显然:

\[dp_i = \sum_{j \in i, \operatorname{lowbit(j)} = \operatorname{lowbit(i)}} dp_{i \oplus j} \times f_j + 1 \times f_j \times g_{i \oplus j} \]

其中 \(dp_{i \oplus j} \times f_j\) 算的是 \(i \oplus j\) 的贡献,$1 \times f_j \times g_{i \oplus j} $ 算的是 \(j\) 的贡献。

code

Cut and Reorder

\(n \le 22\) 考虑状压。注意到最优的方案一定是先做一次操作一然后加上 \(\sum |a_i - b_i|\) 的贡献。我们设操作一后的数组为 \(p\),我们考虑将 \(a\) 数组的数依次按 \(1\)\(n\) 填到 \(p\) 中,\(dp_i\) 表示 \(i\) 集合被填了的方案数,由于我们是依次填的可以通过 \(i\) 算出来下一个该填的 \(a_j\)\(j\),由于一段连续的只消耗 \(c\) 我们可以一段一段填,设我们填了一段 \(t\),那么有转移:

\[dp_{s | t} = \min (dp_{s | t}, dp_s + c + \sum_{k \in t} |a_k - b_k|) \]

枚举 \(s\)\(t\)\(O(n 2^n)\)

注意 \(dp_0\) 应为 \(-c\) 因为填 \(x\) 次数只需 \((x - 1)c\) 的贡献。具体细节看代码。

code

Knapsack with Diminishing Values

首先最朴素的做法就是把每个物品每次买分成一个新物品然后 01 背包,显然这样是合法的。这样的复杂度显然不对。我们发现对于重量 \(w\) 最多有用的东西只有 \(\lceil \frac{W}{w} \rceil\) 个,显然可以二分找到,然后物品的个数只有调和级数个,做完了。

code

Shipping

先设朴素的 \(dp_{i, j}\) 表示第 \(i\) 时刻,前 \(j\) 个物品发货的最小值。显然按照前缀的物品一个一个发货是不劣的,且代价用前缀和是好算的。这样 T 死个人。由于贪心,我们要尽量早的发货,那么我们发现对于每个 \(T_i + k \times X\)\(i \in [1, n]\)\(k\) 为任意正整数)的时间才会发货。于是我们不妨设 \(dp_{i, j, k}\) 表示前 \(i\) 个物品在 \(T_j + k \times X\) 时刻全部发货的最小代价,显然有转移:

\(T_j + (k + 1) \times X \le T_{i + z}\),有:

\[dp_{i + z, i + z, 0} = min\{dp_{i + z, i + z, 0}, dp_{i, j, k} + z \times T_{i + z} - (sum_{i + z} - sum{_i})\} \]

\(T_j + (k + 1) \times X > T_{i + z}\),有:

\[dp_{i + z, j, k + 1} = min\{dp_{i + z, j, k + 1}, dp_{i, j, k} + z \times (T_j + (k + 1) \times X) - (sum_{i + z} - sum{_i})\} \]

其中 \(1 \le z \le K\)

code

Strongly Connected 2

注意到题意可以转换为有多少种选后向边选法使后向边覆盖了 \([1, n]\)。显然有 \(dp_i\) 表示后向边覆盖了 \([1, i]\)。我们可以按照边来转移,对于 \(u \rightarrow v\),设 \(f(v, u)\) 表示终点为 \(v\) 且起点 \(\ge u\) 的个数(其实不是严谨的 \(\ge\)),我们可以先将每个边 \((u, v)\) 按放入一个 vector \(vec_v\),再将每个 \(vec_i\) 从小到大排序,对于边 \(u \rightarrow v\),准确的来说,其 \(f(v, u)\) 应为所在 \(vec_v\) 的大小与其在 \(vec_v\) 中的排名的差,等知道后面计数的方法之后就清楚为什么了。

对于 \(dp\) 的转移,对于 \(v \leftarrow u\),有:

\[1 \le i \le u \quad dp_i = dp_i \times 2 \]

\[dp_v = 2 ^ {f(v, u)} \sum_{u \le i \le v} dp_i \]

显然其实 \(f(v, u)\) 表示可选不选的边数。

线段树维护即可。

code

posted @ 2026-04-30 14:19  ACehomoxue  阅读(21)  评论(0)    收藏  举报