- 代码答疑
【链分解,路径独立集计数】面包题
- @ 2026-7-19 21:14:21
【链分解,路径独立集计数】面包题
题意概括
从 中选择一个子集 。
对于任意被选中的数 ,如果 ,则 不能同时被选中。
需要统计合法子集的数量,并对 取模。
由于 ,无法逐个处理所有整数。关键在于研究限制关系 会形成怎样的结构。
样例分析
以 为例,限制关系为
而数字 不与其他数字产生冲突。
因此,所有数字被分成两部分:
和
3\.
在链 中不能选择相邻元素,合法选择有
共 种。
对于单独的数字 ,可以选或不选,共 种。
两部分互不影响,因此答案为
算法思路
将数字分解成若干条链
对于任意正整数 ,不断除去它所含的因子 ,可以将其唯一表示为
其中 不能被 整除。
例如,当 时:
对应的链起点是 。
固定一个不能被 整除的整数 ,所有形如
且不超过 的数构成一条链。
不同的链不会相交,因为一个整数除去所有因子 后得到的 是唯一的。
同时,题目的冲突只发生在 和 之间。因此,在每条链中,相邻的两个数不能同时选择,不同链之间完全独立。
原问题就转化为:
- 将 分成若干条链。
- 计算每条链中不选择相邻元素的方案数。
- 将所有链的方案数相乘。
一条链的方案数
设 表示长度为 的链中,不选择相邻元素的方案数。
空链只有一种方案,因此
长度为 的链可以选择或不选择,因此
考虑一条长度为 的链,并观察最后一个元素。
如果不选择最后一个元素,那么前 个元素可以任意合法选择,方案数为
如果选择最后一个元素,那么倒数第二个元素必须不选,前 个元素可以任意合法选择,方案数为
所以
这就是斐波那契型递推。
前几个值为
$ f\_0=1,\quad f\_1=2,\quad f\_2=3,\quad f\_3=5,\quad f\_4=8. $
统计每种长度的链有多少条
接下来需要计算长度恰好为 的链有多少条。
一条链的起点 必须满足:
- 不能被 整除。
- 链中至少有 个元素:
- 链中不能有第 个元素:
因此 的范围为
$ \left\lfloor\frac{n}{k^i}\right\rfloor < b \le \left\lfloor\frac{n}{k^{i-1}}\right\rfloor, $
并且 。
设
表示 中不能被 整除的整数个数。
其中共有
个整数能够被 整除,所以
令
那么长度恰好为 的链的数量为
这些链各自有 种选择方式,因此它们对答案的贡献为
最终答案为
如何枚举所有长度
不需要真的计算 ,否则可能发生溢出。
从
开始,每次令
$ q\_i=\left\lfloor\frac{q\_{i-1}}{k}\right\rfloor. $
当 时停止。
因为 且 ,链的最大长度不会超过 。
固定底数快速幂
对于每一层,需要计算
普通快速幂已经能够通过本题。为了降低 组数据下的常数,可以提前预处理
的值。
之后将指数 按二进制分解,只需要乘上对应的预处理结果。
算法流程
对于每组数据:
- 令当前上界 ,链长 。
- 计算下一层上界
- 长度恰好为 的链数为
$
\left(a-\left\lfloor\frac{a}{k}\right\rfloor\right)
\left(b-\left\lfloor\frac{b}{k}\right\rfloor\right). $
- 将答案乘上对应的
- 令 ,链长增加 。
- 重复上述过程,直到 。
正确性说明
引理一:所有整数会被唯一划分为若干条链
任意正整数 都可以不断除以 ,直到所得整数不能被 整除。
因此存在唯一的 ,满足
所以每个整数恰好属于一条以 为起点的链,不会遗漏,也不会属于两条不同的链。
引理二:不同链之间没有选择冲突
题目的冲突只可能发生在 和 之间。
如果
那么
两者除去所有因子 后得到的起点仍然是同一个 。
因此一条限制边的两个端点必然位于同一条链,不同链之间不存在限制关系。
引理三:长度为 的链有 种合法选择
对于链的最后一个元素:
- 不选择它时,前 个元素有 种方案。
- 选择它时,倒数第二个元素不能选择,前 个元素有 种方案。
两类情况互不相交,并覆盖所有合法方案,因此
引理四:算法计算出的 等于长度恰好为 的链数
长度恰好为 的链起点满足
并且 不能被 整除。
区间 中符合条件的数有 个,区间 中符合条件的数有 个。
两者相减得到
正好是长度恰好为 的链数。
定理:算法输出所有合法子集的数量
根据引理一,所有整数被唯一分成若干条链。
根据引理二,不同链的选择互不影响。
根据引理三,每条长度为 的链有 种合法选择。
根据引理四,共有 条长度为 的链,因此所有此类链共有
种组合方式。
将所有链长的贡献相乘,得到的结果不重不漏地统计了全部合法子集。
实现细节与易错点
- 空集也是合法方案,已经包含在每条链的动态规划中。
- 当 时,所有链的长度都是 ,答案自然变成
- 不要直接计算 。通过不断整除 可以避免溢出。
- 链数量和指数最大达到 ,需要使用
long long。 - 模乘中的两个数均小于 ,使用
long long足以容纳乘积。 - 在本题范围内,链长最多为 ,数组开到 即可。
参考实现
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll md = 998244353;
ll f[35], pw[35][31];
ll qp(int x, ll e) {
ll r = 1;
while(e) {
int b = __builtin_ctzll(e);
r = r * pw[x][b] % md;
e &= e - 1;
}
return r;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
f[0] = 1;
f[1] = 2;
for(int i = 2; i < 35; i++) {
f[i] = (f[i - 1] + f[i - 2]) % md;
}
for(int i = 1; i < 35; i++) {
pw[i][0] = f[i];
for(int j = 1; j <= 30; j++) {
pw[i][j] = pw[i][j - 1] * pw[i][j - 1] % md;
}
}
int t;
cin >> t;
while(t--) {
ll n, k;
cin >> n >> k;
ll ans = 1;
ll a = n;
int len = 1;
while(a) {
ll b = a / k;
ll cnt = (a - b) - (b - b / k);
ans = ans * qp(len, cnt) % md;
a = b;
len++;
}
cout << ans << '\n';
}
return 0;
}
复杂度分析
对于一组数据,整数 最多被除以 共
次。
每次计算一次固定底数的二进制幂,指数最多有
个二进制位。
因此单组数据的最坏时间复杂度为
由于 ,两层循环的长度都不超过约 。
总时间复杂度为
预处理数组规模为常数,空间复杂度为
京公网安备11010802045784号
YIZHIYANG 一只羊 LV 9