1 条题解
-
0
「自指01序列」题解
问题分析
我们需要计算长度为 的 0-1 序列 的数量,满足:
- 对于每个长度为 的连续子段(共 个)
- 将该子段看作 位二进制数(左侧高位,右侧低位),设为
- 要求 (即序列的第 位必须是 1)
换句话说,序列 必须满足:
$$\forall i \in [0, n-k], \quad s_{\text{bin}(s_i s_{i+1} \cdots s_{i+k-1})} = 1 $$其中 表示将二进制串 转换为十进制数。
关键约束
设窗口 对应的数值为 ,则要求 。
这意味着:每个长度为 的窗口对应的数值,必须是一个 1 的位置索引。
重新理解问题
我们可以将序列 看作一个有向图上的路径:
1. 状态表示
考虑一个滑动窗口,当前窗口内容是 位二进制数。当我们向右移动一位时,窗口内容从 变为:
- 去掉最高位,左移一位
- 加上新的最低位
即:,其中 。
因此,我们可以将每个 位二进制数看作一个状态,共 个状态。
2. 转移条件
设当前窗口对应的数值(状态)为 ,那么根据条件, 必须是 1。
但 是什么? 是当前窗口的数值,而 是序列第 位的值。这个值会影响未来的某个窗口吗?
实际上, 是窗口数值,而 是序列中位置 的值。这个值会在我们滑动到某个位置时被检查,但不是立即检查。
更准确地说:当窗口滑动到位置 时,我们检查 是否为 1,其中 是窗口在位置 时的数值。
但 是序列的固定位置,而窗口在不断滑动。这建立了一个自指约束:窗口数值指向序列的某个位置,该位置的值必须是 1。
图论建模
状态机模型
定义状态为当前窗口的 位二进制值,共 个状态,记为 。
序列 对应一条长度为 的状态路径:
- 初始窗口:前 位 对应状态
- 第 步:添加 (如果是前 步,则是构建初始窗口;之后是滑动)
约束:对于路径上的每个状态 (即某个窗口的数值),要求 。
但 不是当前状态,而是序列第 位的值。这个值会在路径的某个位置出现:当窗口滑动到某个位置 时,如果窗口数值等于 ,那么我们要求 。
而 是序列的第 位,它会影响以 开头的窗口吗?不一定。
关键观察
设 是一个 位二进制数(状态)。如果 作为窗口数值出现在路径中(即某个时刻窗口内容等于 ),那么根据条件, 必须是 1。
但 是序列的第 位,而 是状态值。 会影响哪些窗口呢?
考虑位置 :以 开头的窗口内容是 ,这个窗口的数值是某个 。条件要求 。
所以这形成了一个链式约束。
建立约束图
定义一个有向图 :
- 节点: 个状态( 位二进制数)
- 边:如果状态 可以通过添加一个比特 转移到状态 ,则存在边 ,标记为
具体转移:
序列 对应图上一条长度为 的路径(从某个初始状态开始,经过 次转移)。
约束:对于路径上的每个节点 (即某个窗口数值),要求 。
但 是什么?它是序列的第 位的值,这个值由路径上的某条边决定。
更准确地说:路径的第 步(从0开始计数)输出一个比特 ,这个比特是转移的标签。
所以约束变为:对于路径上的每个节点 ,要求在第 步输出的比特是 1。
重新表述
我们有一条长度为 的路径,路径上的每个位置 有一个状态 (窗口内容)和一个输出比特 。
约束:对于每个位置 ,。
即:位置 的状态值 指向另一个位置 ,要求位置 的输出比特为 1。
这是一个自指约束:每个位置的状态指向另一个位置,要求该位置的输出为 1。
动态规划算法
状态定义
由于 ,,状态数很少。
我们可以在 DP 中记录当前路径的最后 个比特(即当前状态),以及已经确定的输出比特中哪些位置必须是 1。
但约束涉及未来位置:当前状态 要求位置 的输出为 1。如果 已经过去,我们检查是否满足;如果 在未来,我们记录这个要求。
定义 DP 状态:
- 当前位置 (0 到 )
- 当前窗口状态 ( 位二进制数)
- 一个集合 ,表示哪些未来位置必须输出 1
但 可能很大( 可达 500),不能直接记录。
关键简化
由于 很小,状态值 的范围是 ,即 。
因此,约束只涉及位置 0 到 15(如果 )或更少。
所以,只有前 个位置的约束是关键的。对于位置 ,没有状态值会指向它(因为状态值最大为 )。
重要结论:只有前 个位置 受到直接约束。其他位置 ()没有状态指向它们,所以它们可以是 0 或 1 自由选择。
但等等:状态值 指向位置 ,而 ,所以确实只涉及前 个位置。
因此,约束简化为:对于每个位置 ,如果 ,则 。
其中 。
新的理解
序列 的前 个比特是受约束的,后面的比特自由。
我们可以先确定前 个比特,然后检查是否满足所有约束,最后乘以 (后面每个位置自由选择 0 或 1)。
但约束是动态的: 依赖于序列的值。
建立自动机
由于 很小,我们可以构建一个自动机,状态是当前窗口内容( 位),并且记录哪些位置已经满足了约束。
定义状态为 :
- :当前窗口内容( 位)
- :一个 位的位掩码,表示哪些位置已经确认为 1
初始状态:
转移:添加一个比特 :
- 新状态
- 新掩码
- 但我们需要添加约束:当前状态 要求位置 必须是 1。如果 对应的比特在 中尚未设置,我们需要设置它,但前提是 在当前路径中出现了。
实际上,约束是:如果状态 出现在路径中,那么 。
在自动机中,当我们处于状态 时,我们要求 。但 是什么时候输出的?它是路径的第 步的输出。
所以我们需要在自动机中记录:哪些位置已经输出了 1。
更精确的自动机
考虑构建序列的过程:我们逐步输出 。
在输出 时,当前窗口状态是基于最近的 个输出。
我们需要确保:对于每个 ,如果存在某个 使得 ,那么 。
等价地:对于每个 , 必须为 1,除非没有状态等于 。
因此,问题转化为:有多少个序列 ,使得集合 与所有状态值集合不相交?
最终算法
步骤1:枚举所有可能的状态值集合
由于 ,状态值范围 最多 16 个值。
对于序列 ,设 是 1 的位置集合。
约束要求:所有出现在路径中的状态值都必须属于 。
但哪些状态值会出现在路径中?这取决于序列 本身,因为状态由连续的 个比特决定。
步骤2:固定前 个比特
由于状态值小于 ,只有前 个位置可能被要求为 1。
我们可以枚举前 个比特的所有可能( 种,最多 ),对于每种枚举:
- 检查是否自洽:对于每个位置 ,如果 ,那么是否可能没有状态等于 ?
- 计算满足条件的序列总数(乘以 )
但检查自洽性需要知道哪些状态会出现,这又依赖于序列。
步骤3:建立约束系统
定义变量 (0 或 1),。
约束:对于每个窗口起始位置 : 设 要求
这是一个自指约束系统。
我们可以尝试直接求解这个约束系统。
步骤4:注意到 很小,使用 DP
由于 ,我们可以使用状态压缩 DP。
定义 表示:
- 已经确定了前 个比特
- 当前窗口的最后 个比特(即状态)为某个值,但我们需要记录更多
更好的定义: :
- :已经处理的位置数
- :当前窗口状态(最近 个比特)
- :一个 位的掩码,表示哪些位置已经被确认为必须为 1
转移:添加下一个比特 :
- 新状态
- 新要求:当前状态 要求位置 为 1。所以如果 ,我们已经知道 的值,检查是否满足;如果 ,记录这个要求。
- 但 掩码记录的是未来哪些位置必须为 1。
具体地:
- 当处于状态 时,如果 ,检查 是否为 1
- 如果 ,则要求 ,设置 中的对应位
最终,在 时,检查所有要求是否满足( 中要求为 1 的位置,对应的 值是否为 1)。
步骤5:简化 DP
由于 很小,我们可以将 和部分历史信息编码。
实际上,我们只需要知道最近 个比特,因为窗口只依赖这些。
DP 状态:
- :0 到
- : 位状态,
- : 位掩码,表示哪些位置必须为 1
转移:对于 :
- 检查约束:状态 要求
- 如果 ,已经确定了 ,检查是否满足
- 如果 ,在 中标记位置 必须为 1
- 但 的值是什么?对于 ,我们需要知道之前选择的 。这没有记录在状态中!
因此,我们需要记录历史选择的比特。
步骤6:记录必要历史
由于 ,我们只需要知道前 个比特的值。
所以我们可以将前 个比特的选择作为状态的一部分。
定义状态:,其中 是前 个比特的选择( 位掩码)。
但 可能大于 ,此时 已经固定。
对于 , 不再变化。
转移:
- 如果 :选择 ,更新
- 检查约束:状态 要求 ,从 中检查(如果 )或记录要求
但记录要求需要另一个掩码。
最终可行算法
由于 ,,我们可以暴力枚举前 个比特的所有可能(最多 65536 种)。
对于每种前 个比特的赋值,检查是否满足所有约束:
- 对于每个窗口位置 (因为 ,且只需要检查到 就能覆盖所有可能指向前 个位置的约束)
- 计算
- 检查 是否为 1
如果满足,那么这种赋值是可行的。对于后面的 个比特,可以自由选择 0 或 1,贡献 。
因此,答案 = (满足条件的前 比特赋值数) × 。
但需要验证:对于 的窗口, 可能 ,此时约束 自动满足(因为 的位置可以自由选择为 1)。
实际上,对于 ,窗口包含位置 ,这些位置的值是自由的。但 可能仍然 。
例如,,序列长度 ,。窗口位置 :,如果 ,则 ,要求 。这仍然涉及前 4 个位置。
所以我们需要检查所有窗口,直到 。
因此,算法为:
- 枚举前 个比特的所有赋值( 种)
- 对于每种赋值,检查所有窗口 的约束
- 实际上需要检查 ,但后面的窗口可能涉及自由位
- 如果所有约束满足,贡献
复杂度:,对于 ,,,,可接受。
验证样例
,
枚举前 4 个比特 :
窗口:
- :,要求
- :,要求
- :,要求
检查所有 种赋值:
- :, ✓;, ✓;, ✓
- :同样满足
其他都不满足。所以 2 种。
后面 个自由位,贡献 。
总方案数 2,与样例一致。
总结
最终算法:
- 枚举前 个比特的所有可能赋值
- 对于每种赋值,检查所有窗口约束
- 统计满足条件的赋值数
- 答案 =
由于 ,,枚举 种情况可行。
- 1
信息
- ID
- 5914
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者