[Codeforces 1716D] Chip Move
https://codeforces.com/contest/1716/problem/D
题意:
从点 \(0\) 出发,第 \(i\) 步只能走 \(k + i - 1\) 的倍数步长,问到达 \(1\) ~ \(n\) 的方案数。
思路:
因为每次 \(k\) 都会 \(+1\) ,所以最坏情况下走的步长和为一个等差数列求和,因此我们至多只会走 \(\sqrt{n}\) 步。有个很显然的 \(dp\) 做法,\(dp[i][j]\) 代表到点 \(i\) 时,用了 \(j\) 步的方案数,但是数组太大了开不下,并且该状态向后面转移时需要枚举所有能到的点,时空复杂度都不够优秀。
为了避免枚举后续能到的点,我们考虑填表法。我们枚举步长 \(i\) ,当前点 \(j\) ,前面合法的点为上一次能到的点 \(x\) ,并且 \(x \equiv j \pmod{i}\)。那么一边走一边把这些点的贡献加入一个桶就行。时间复杂度 \(O(n\sqrt{n})\) 。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
void add(int &a, int b) {
a += b;
if (a >= mod) a -= mod;
}
int dp[200005], pre[200005], ans[200005];
void solve() {
int n, k;
cin >> n >> k;
pre[0] = 1;
for (int i = k, sum = k; sum <= n; sum += ++i) {
for (int j = sum - i; j <= n; ++j) {
if (j < sum) {
add(pre[j % i], dp[j]);
continue;
}
int tmp = dp[j];
dp[j] = pre[j % i];
add(pre[j % i], tmp);
add(ans[j], dp[j]);
}
for (int j = 0; j <= i; ++j) pre[j] = 0;
}
for (int i = 1; i <= n; ++i) {
cout << ans[i] << ' ';
}
}
int main() {
#ifndef stff577
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
cout << fixed << setprecision(20);
#endif
int t = 1;
while (t--) solve();
return 0;
}