D. Chip Move(DP,优化时间和空间)

题意
在一维坐标中,从 0 位置开始跳。
第 1 次移动可以跳 k 的倍数距离,第 2 次移动可以跳 k+1 的倍数距离,…,第 m 次跳可以跳 k+m-1 的倍数距离。
问,跳到 1~n 位置的方案数分别为多少?

1 ≤ k ≤ n ≤ 2 ⋅ 1 0 5 ,   a n s   m o d   998244353 1≤k≤n≤2⋅10^5,\ ans \bmod 998244353 1kn2105, ansmod998244353

思路
朴素做法:
定义两维状态 f[i, j] 表示,到 i 位置时已经跳了 j 步的方案数。
当 k 最小为 1 时,每次跳只跳距离的一倍,n 最大为 2e5, m 2 + m ≤ 2 ∗ n m^2 + m \leq 2*n m2+m2n,所以最多跳 640 次。
遍历所有位置 i,遍历跳的次数 j,遍历上一次跳的倍数 b,所以转移方程为:
f[i][j] += f[i - b*(k+j-1)][j-1];
时间复杂度 O ( n 2 ∗ 640 ) O(n^2*640) O(n2640),空间复杂度 O ( n ∗ 640 ) O(n*640) O(n640)。 时间复杂度和空间复杂度都超限。

cin >> n >> k;
	
f[0][0] = 1;
for(int i=1;i<=n;i++)
{
	int sum = 0;
	for(int j=1; j*(j+1) <= 2*n; j++)
	{
		for(int b=1; i - b*(k+j-1) >= 0; b++)
			f[i][j] += f[i - b*(k+j-1)][j-1];
	}
	cout << sum << " ";
}

需要想办法优化。
能不能像优化完全背包那样,把枚举倍数的这一重循环优化掉?
优化后的转移方程是这样的:
f[i][j] = f[i - (k+j-1)][j] + f[i - (k+j-1)][j-1];
对于走到当前 i 位置跳了 j 步,可以由 走到上一次跳的位置 i-k+j-1 跳了 j 步的方案数 加上 走到上一位置跳了 j-1 步的方案数。
走到上一位置跳了 j 步,再到当前位置跳了 j 步,就相当于从一开始的位置跳了 i-k+j-1 的倍数步了,这个倍数至少为 2。
再加上倍数为 1 的方案数 f[i-(k+j-1)][j-1],走到上个位置跳了 j-1 步,再跳 1 倍的距离到当前位置。

优化掉一维后的代码:

cin >> n >> k;

f[0][0] = 1;
for(int i=1;i<=n;i++)
{
	int sum = 0;
	for(int j=1; j*(j+1) <= 2*n; j++)
	{
		if(i >= k+j-1)
		{
			f[i][j] = f[i - (k+j-1)][j] + f[i - (k+j-1)][j-1];
			sum += f[i][j];
		}
	}
	cout << sum << " ";
}

时间复杂度 O(n*640),但是还需要优化空间。

观察状态转移方程 f[i][j] = f[i - (k+j-1)][j] + f[i - (k+j-1)][j-1] 发现,当前更新 f[i, j] 只用到了 f[j] 和 f[j-1],只用到了当前层和上一层。
如果只用到了上一层的话可以用滚动数组优化,现在也用到了当前层,所以可以再开一个数组交替更新。

当前步数用到了当前层和上一层,为了方便数组交替,将枚举位置和枚举步数的两重循环互换,先枚举步数再枚举位置。

t[0] = 1;
for(int j=1; j*(j+1) <= 2*n; j++) //交换遍历顺序
{
	for(int i=1;i<=n;i++) //更新状态
	{
		if(i >= k+j-1) f[i] = (f[i - (k+j-1)] + t[i - (k+j-1)]) % mod;
	}
	for(int i=0;i<=n;i++) //交替数组,数组重置,注意从0开始
	{
		ans[i] = (ans[i] + f[i]) % mod, t[i] = f[i], f[i] = 0; 
	}
}

for(int i=1;i<=n;i++) cout << ans[i] << " ";

Code

//https://codeforces.com/contest/1716/problem/D
#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 200010, mod = 998244353;
int T, n, m;
int a[N];
int f[N], t[N];
int ans[N];

signed main(){
	Ios;
	int k; cin >> n >> k;
	
	t[0] = 1;
	for(int j=1; j*(j+1) <= 2*n; j++)
	{
		for(int i=1;i<=n;i++)
		{
			if(i >= k+j-1) f[i] = (f[i - (k+j-1)] + t[i - (k+j-1)]) % mod;
		}
		for(int i=0;i<=n;i++) ans[i] = (ans[i] + f[i]) % mod, t[i] = f[i], f[i] = 0;
	}
	
	for(int i=1;i<=n;i++) cout << ans[i] << " ";

	return 0;
} 

优化确实强,要多积累这样的模型。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值