前缀合优化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]);
	}
}
posted @ 2022-09-01 11:20  seekerHeron  阅读(66)  评论(0)    收藏  举报