嘉义县网站建设_网站建设公司_React_seo优化
2026/1/21 19:55:25 网站建设 项目流程

Codeforces 1716D Chip Move 题解

此文章已同步在洛谷上更新

题目大意

洛谷题面
如果想看英文题面请去 CF
给定两个数 \(n,k\),问从 \(0\) 开始,第 \(i\) 步只能走 \((k+i-1)\) 的正倍数(即不能走 \(0\)),问分别走到 \(x\in[1,n]\) 的方案数,对 \(998244353\) 取模。

思路

第一步:将原问题转化成背包

看到第 \(i\) 步只能走 \((k + i - 1)\) 的整倍数,不难发现和完全背包很类似。
可以把第 \(i\) 步看做只能走 \((k + i - 1)\),但是能走无限次。
这样就能把这个问题转化成一个完全背包问题:有 \(n\) 个物品,第 \(i\) 个物品重量为 \((k + i - 1)\),每个物品最少取一个(因为题目中说不能走 0)问你总重量为 \(x\in[1,n]\) 的取法数。

第二步:设计状态

但这个问题不是裸的完全背包板子,因为原问题是求最大收益,现在的问题是求方案数。
所以现在需要用完全背包的状态修改一下。
完全背包的状态:设 \(dp_{i,j}\) 为到第 \(i\) 个物品,总重量为 \(j\) 时的最大收益。
现在只要修改一点,把最大收益改成方案数就行。
现在的状态:设 \(dp_{i,j}\) 为到第 \(i\) 个物品,总重量为 \(j\) 时的方案数。

第三步:转移方程

原来的完全背包转移方程是 \(dp_{i,j} = \max(dp_{i-1,j}, dp_{i,j-w_i} + v_i)\)
现在需要在原来的基础上修改几点:

  • 把原方程转换成统计方案的形式
  • 把原来的 \(dp_{i-1,j}\) 删去,不能转移,每个物品最少取一个。
  • 因为没有了 \(dp_{i-1,j}\),所以统计 \(dp_{i,j}\) 时需要加上 \(dp_{i-1,j-w_i}\)
  • 需要把方程中的 \(w_i\) 替换成 \((k+ i - 1)\)

现在的转移方程就变成了 \(dp_{i,j} = dp_{i,j-(k+i-1)} + dp_{i-1,j-(k+i-1)}\)

初始状态与答案

初始时没有开始选,即选到第 0 个物品,总重量为 0,但是有一种方法(什么也不选)。
初始状态是 \(dp_{0,0} = 1\)
由于在原题中走几步到达终点都算答案,所以统计最终答案时要加上每一个 \(i\) 对应的 dp 值。
答案为 \(ans_x = \sum_{i=1}^n{f_{i,x}}\)

代码实现

实现细节

建议看完此部分后再看代码。

细节 1:时间优化

按照这种方法,时间复杂度为 \(O(n^2)\),无法通过此题,考虑优化。
第二维总重量无法优化,只能优化第一位物品个数。
因为每种物品最少取一个,所以按照取一个计算,这样能让总重量最少。
要让总重量 \(\le n\),也就是让 \(\sum_{i=1}^x{(k + i - 1)}\le n\)
解得 \(x \le \sqrt{2n}\),也就是说最多取 \(\sqrt{2n}\) 个物品就能取完 \(n\) 的容量,因此 \(i\) 只用枚举到 \(\sqrt{2n}\) 就行。
优化后的时间复杂度为 \(O(n \sqrt{n})\)

细节 2:滚动数组优化

如果直接用上面的状态,那空间复杂度为 \(O(n \sqrt{n})\),无法通过此题(开 int 数组需要用 360MB 的空间)。
考虑将第一维滚动掉。
但是这里不能直接压掉一维,需要在第一维再开一个 0/1 表示这轮和上轮的 dp 值。
所以需要再开一个变量 now,表示当前的 dp 值存在 \(dp_{now,i}\) 里。那上一轮的 dp 值就存在 \(dp_{now \oplus 1,i}\) 里。
每次转移的时候需要让 \(now \gets now \oplus 1\),也就是让当前的 dp 值变成下一轮的上一轮的 dp 值。
转移方程也要进行一些改动,变成 \(dp_{now,j} = dp_{now,j-(k+i-1)} + dp_{now \oplus 1,j-(k+i-1)}\)
优化后的空间复杂度为 \(O(n)\)

细节 3:统计答案

由于滚动数组无法记录所有状态,所以不能在最后统计最终答案,而是要在 dp 过程中累加答案。
也就是在 dp 的过程中将 \(ans_j\) 累加上 \(dp_{now,j}\)

一些坑点

  • 记得取模。
  • \(n,k\) 的范围是 \(1 \le n,k \le 2\cdot10^5\),不是 \(1 \le n,k \le 10^5\)
  • 记得要及时清空数组(不然会 WA)。
  • 其他的注意事项可以看代码注释

Code(只展示关键代码)

f[0][0] = 1;//初始值
int now = 0;
for (int i = 1; i * i <= n * 2; i++){//i枚举到sqrt(2*n)now ^= 1;for (int j = i * k + i * (i - 1) / 2; j <= n; j++)//取前i个物品,最少总重量为ik+i*(i-1)/2(读者自行推导),所以j从ik+i*(i-1)/2开始(f[now][j] += f[now][j - (k + i - 1)] + f[now ^ 1][j - (k + i - 1)]) %= mod;//转移memset(f[now ^ 1], 0, sizeof(f[now ^ 1]));//注意:要清空数组for (int j = 1; j <= n; j++)//累加答案(ans[j] += f[now][j]) %= mod;
}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询