前缀合优化dp一则Chip Move
Chip Move
爬楼梯,每次爬都要爬\(k\)的倍数,并且爬完一次,\(k+1\)一次,共\(n\)阶楼梯,问爬到点$1至n $次各有多少种可能。
解析
考虑最多能爬\(\sqrt{x}\)次,\(\max{k}=\sqrt{x}\),则对于每爬一次进行\(dp\)。
令状态为\(dp[k][i]\),\(k\)表示正在爬第\(k\)次,\(i\)表示爬到了第\(i\)个点。
则\(dp[k][i]=\sum{dp[k-1][j]((i-j)\%k=0)}\),我们可以对于这个状态进行记忆化,简单的说,就是用一种类似前缀合的,把所有状态都记录在\(i+k\)上。
以下是王超老师的做法:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int N = 2e5+7;
ll f[N],s[N],ans[N];
int main() {
ll n,k;
scanf("%lld%lld",&n,&k);
ll sum = 0;
f[0] = 1;
for(; sum <= n && k <= n ; ) {
for(int i = 0 ; i <= n ; i++) {
ll t = i - k >= 0 ? s[i-k] : 0;
s[i] = (t+f[i])%mod;
f[i] = t;
ans[i] = (ans[i] + f[i]) % mod;
}
sum+=k++;
}
for(int i = 1 ; i <= n ; i++) {
printf("%lld ",ans[i]);
}
}
这太简洁了,对我来说难以理解,对吗?
因此,我重新推了一遍dp公式:
\[dp[k][i]=sum[k-1][i-k]
\\
sum[k][i]=sum[k-1][i-k]+sum[k][i-k-1]
\]
\(sum\)表示\(\sum{dp[k-1][j]((i-j)\%k=0)}\),由于\(sum\)是为了下一步转移,因此应当从这一步的\(i-k-1\)的状态和上一步\(i-k\)的状态走过来。
内存会爆,使用滚动数组优化即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int N = 2e5+7;
const int K = 500+7;
ll dp[2][N],s[2][N],ans[N];
int main() {
ll n,k;
scanf("%lld%lld",&n,&k);
ll sum = 0;
for(int i = 0 ; i <= n ; i+=k)
s[0][i] = 1;
for(int j = 1 ; sum <= n && k <= n ; j++) {
for(int i = 0 ; i <= n ; i++) {
int t1 = i-k>=0?s[(j-1)%2][i-k]:0;
int t2 = i-k>=0?s[j%2][i-k-1]:0;
dp[j%2][i] = t1%mod;
s[j%2][i] = (dp[j%2][i] + t2)%mod;
ans[i]+=dp[j%2][i];
ans[i]%=mod;
}
sum += k++;
}
for(int i = 1 ; i <= n ; i++) {
printf("%lld ",ans[i]);
}
}