1 条题解
-
0
CF1989E Distance to Different 题解
题意回顾
给定整数 和 (,)。构造一个长度为 的数组 ,其中每个元素 ,且 每个整数至少出现一次。
由 生成数组 :$b_i = \min\limits_{j \in [1,n],\, a_j \neq a_i} |i - j|$,即 是到最近的 与 不同的元素 的距离。
求在所有满足条件的 中,不同的 数组有多少个,答案对 取模。
核心观察: 数组的结构
设 中颜色变化的边界位置集合为 。对于一段连续相同颜色的区间 ,有:
- 若 是边界( 或 ),且另一侧存在不同颜色,则 ;
- 若 在区间内部,则 。
因此 数组完全由 每个颜色段的长度 和 边界的位置 决定。具体来说:
- 相邻两段之间必然有 (若边界不在数组首尾)。
- 在段内部, 值从边界向中间每次 ,形成“山峰”形状。
- 不可能出现两个相邻的 (否则中间那段长度为 ,不合法)。唯一的例外是数组末尾位置 :如果最后一段长度恰好为 ,则 且 ,这是允许的。
综上,一个合法的 数组等价于选择一组“ 的位置”(标记颜色变化),满足:
- 任意两个 之间至少间隔一个位置(除非在末尾可以连续);
- 的个数决定了段数,段数必须 (因为要使用至少 种颜色,且相邻段颜色不同)。
给定 的位置后, 数组的其他位置被唯一确定:两个 之间的区间填成递增再递减的“山峰”。
因此问题转化为:统计选择 的位置的方案数,满足段数 ,且相邻段颜色不同的染色方案数。由于 ,我们可以用 DP 同时处理段的划分和颜色分配。
DP 状态设计
设 表示:考虑前 个位置,恰好形成了 个颜色段,且第 个位置恰好是一个段的末尾(即 或 时允许末尾不是 )的方案数。
为了正确转移,我们维护前缀和 ,含义是“前 个位置中,最后一个段结束位置 且段数为 的方案数”。
初始状态:,表示空序列有 段。
转移:
当我们考虑第 个位置作为当前段的末尾时,设其前一个段结束于位置 (,且当 时不能有 ,否则出现相邻的 ),那么段数增加 ,并且我们需要为新段分配一种颜色(不同于前一段的颜色)。由于颜色分配与 的形状无关,并且我们只关心最终使用了多少种不同颜色,因此 直接表示段数即可(段数 使用颜色数,最后再乘组合数?这里实际上在 DP 内部已经隐含了颜色分配的因子,具体看下文)。
核心转移式():
$$f[i][j] = sum[i-1][j-1] \quad-\quad [i>2 \land i \neq n] \cdot f[i-2][j-1] $$其中 是减去 的情况(相邻的 ,非法),但当 时允许相邻(最后一段长 ),因此不做减法。当 时也不可能出现合法的相邻 ,故也不减。
对于 的情况(段数超过 也是允许的,只要颜色数达到 ),转移中需要合并全部段数 的状态:
$$f[i][k+1] = sum[i-1][k] + sum[i-1][k+1] \quad-\quad [i>2 \land i \neq n] \cdot \bigl(f[i-2][k] + f[i-2][k+1]\bigr) $$这代表“前 个位置已经有了 段或 段,现在新开一段(或末尾闭合)”的总和,同样减去非法相邻情况。
前缀和维护:
$$sum[i][j] = \bigl(sum[i-1][j] + f[i][j]\bigr) \bmod p $$其中 恒成立。
答案计算
DP 结束后,要求段数 且每种颜色至少出现一次。对于恰好 段的情况,段数等于颜色数,染色方案唯一(相邻段颜色不同,且 种颜色全用,必须是排列 种颜色的某种交替模式)。对于 段的情况,我们需要保证 种颜色都出现,这等价于段数 的合法 序列都对应了有效的颜色分配(因为总可以给多余的段重复某些颜色)。实际上,通过组合推导可以得到:最终合法的 数组数量恰好等于
其中 对应恰好 段(颜色全用), 对应段数 的所有情况(被 状态吸收)。这是因为 DP 中的 直接对应段数,而颜色分配方案已隐含在转移中(相邻段必须不同颜色,保证了至少需要 种颜色;段数 必然可以分配满 种颜色)。
复杂度
- 状态数 ,其中 ,。
- 转移 ,总复杂度 ,非常轻松。
参考代码
#include <iostream> #include <cstdio> using namespace std; const int N = 200010; const long long p = 998244353; int n, k; long long f[N][12], sum[N][12]; int main() { cin >> n >> k; f[0][0] = sum[0][0] = 1; for (int i = 1; i <= n; i++) { sum[i][0] = 1; // 0 段只有空集 for (int j = 1; j <= k; j++) { // 新开一段,从前 i-1 个位置中段数为 j-1 的状态转移 f[i][j] = sum[i-1][j-1]; if (i > 2 && i != n) f[i][j] = (f[i][j] - f[i-2][j-1] + p) % p; // 减去相邻 1 的非法情况 else f[i][j] = (f[i][j] + p) % p; sum[i][j] = (sum[i-1][j] + f[i][j]) % p; } // 处理段数 >= k 的情况 (k+1 状态) f[i][k+1] = (sum[i-1][k] + sum[i-1][k+1]) % p; if (i > 2 && i != n) f[i][k+1] = (f[i][k+1] - (f[i-2][k] + f[i-2][k+1]) % p + p) % p; sum[i][k+1] = (sum[i-1][k+1] + f[i][k+1]) % p; } long long ans = (f[n][k] + f[n][k+1]) % p; cout << ans << endl; return 0; }注意:模数 是质数,代码中的减法均加上 保证非负。
- 1
信息
- ID
- 6891
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者